From ef4797497928ee965b9652e5d1ec6acc8f0ad6dd Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:43:19 +0900
Subject: [PATCH 1/9] adding README.md for s key
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 39b6ec9..f3692e2 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ If you want to add connections, you need to edit your config file. For more info
| h, j, k, l | Scroll left/down/up/right |
| Ctrl + u, Ctrl + d | Scroll up/down multiple lines |
| g , G | Scroll to top/bottom |
+| s | Sort by selected column |
| H, J, K, L | Extend selection by one cell left/down/up/right |
| y | Copy a cell value |
| ←, → | Move focus to left/right |
From ceda08c9ac3704a09f8bf736791c8ea1ff03866e Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:44:50 +0900
Subject: [PATCH 2/9] add Order and OrderManager in Table Components
---
src/components/table.rs | 176 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 174 insertions(+), 2 deletions(-)
diff --git a/src/components/table.rs b/src/components/table.rs
index 0c5da62..7efc5be 100644
--- a/src/components/table.rs
+++ b/src/components/table.rs
@@ -17,11 +17,91 @@ use tui::{
};
use unicode_width::UnicodeWidthStr;
+#[derive(Debug, PartialEq)]
+struct Order {
+ pub column_number: usize,
+ pub is_asc: bool,
+}
+
+impl Order {
+ pub fn new(column_number: usize, is_asc: bool) -> Self {
+ Self {
+ column_number,
+ is_asc,
+ }
+ }
+
+ fn query(&self) -> String {
+ let order = if self.is_asc { "ASC" } else { "DESC" };
+
+ return format!(
+ "{column} {order}",
+ column = self.column_number,
+ order = order
+ );
+ }
+}
+
+#[derive(PartialEq)]
+struct OrderManager {
+ orders: Vec,
+}
+
+impl OrderManager {
+ fn new() -> Self {
+ Self { orders: vec![] }
+ }
+
+ fn generate_order_query(&mut self) -> Option {
+ let order_query = self
+ .orders
+ .iter()
+ .map(|order| order.query())
+ .collect::>();
+
+ if !order_query.is_empty() {
+ return Some("ORDER BY ".to_string() + &order_query.join(", "));
+ } else {
+ return None;
+ }
+ }
+
+ fn generate_header_icons(&mut self, header_length: usize) -> Vec {
+ let mut header_icons = vec![String::new(); header_length];
+
+ for (index, order) in self.orders.iter().enumerate() {
+ let arrow = if order.is_asc { "↑" } else { "↓" };
+ header_icons[order.column_number - 1] =
+ format!("{arrow}{number}", arrow = arrow, number = index + 1);
+ }
+ return header_icons;
+ }
+
+ fn add_order(&mut self, selected_column: usize) {
+ let selected_column_number = selected_column + 1;
+ if let Some(position) = self
+ .orders
+ .iter()
+ .position(|order| order.column_number == selected_column_number)
+ {
+ if self.orders[position].is_asc {
+ self.orders[position].is_asc = false;
+ } else {
+ self.orders.remove(position);
+ }
+ } else {
+ let order = Order::new(selected_column_number, true);
+ self.orders.push(order);
+ }
+ }
+}
+
pub struct TableComponent {
pub headers: Vec,
pub rows: Vec>,
pub eod: bool,
pub selected_row: TableState,
+ orders: OrderManager,
table: Option<(Database, DTable)>,
selected_column: usize,
selection_area_corner: Option<(usize, usize)>,
@@ -36,6 +116,7 @@ impl TableComponent {
selected_row: TableState::default(),
headers: vec![],
rows: vec![],
+ orders: OrderManager::new(),
table: None,
selected_column: 0,
selection_area_corner: None,
@@ -58,6 +139,7 @@ impl TableComponent {
headers: Vec,
database: Database,
table: DTable,
+ hold_cusor_position: bool,
) {
self.selected_row.select(None);
if !rows.is_empty() {
@@ -65,7 +147,11 @@ impl TableComponent {
}
self.headers = headers;
self.rows = rows;
- self.selected_column = 0;
+ self.selected_column = if hold_cusor_position {
+ self.selected_column
+ } else {
+ 0
+ };
self.selection_area_corner = None;
self.column_page_start = std::cell::Cell::new(0);
self.scroll = VerticalScroll::new(false, false);
@@ -77,6 +163,7 @@ impl TableComponent {
self.selected_row.select(None);
self.headers = Vec::new();
self.rows = Vec::new();
+ self.orders = OrderManager::new();
self.selected_column = 0;
self.selection_area_corner = None;
self.column_page_start = std::cell::Cell::new(0);
@@ -89,6 +176,18 @@ impl TableComponent {
self.selection_area_corner = None;
}
+ pub fn add_order(&mut self) {
+ self.orders.add_order(self.selected_column)
+ }
+
+ pub fn generate_order_query(&mut self) -> Option {
+ return self.orders.generate_order_query();
+ }
+
+ pub fn generate_header_icons(&mut self) -> Vec {
+ return self.orders.generate_header_icons(self.headers.len());
+ }
+
pub fn end(&mut self) {
self.eod = true;
}
@@ -522,6 +621,7 @@ impl Component for TableComponent {
out.push(CommandInfo::new(command::extend_selection_by_one_cell(
&self.key_config,
)));
+ out.push(CommandInfo::new(command::sort_by_column(&self.key_config)));
}
fn event(&mut self, key: Key) -> Result {
@@ -568,7 +668,7 @@ impl Component for TableComponent {
#[cfg(test)]
mod test {
- use super::{KeyConfig, TableComponent};
+ use super::{KeyConfig, Order, OrderManager, TableComponent};
use tui::layout::Constraint;
#[test]
@@ -588,6 +688,78 @@ mod test {
assert_eq!(component.rows(1, 2), vec![vec!["1", "b"], vec!["2", "e"]],)
}
+ #[test]
+ fn test_query() {
+ let asc_order = Order::new(1, true);
+ let desc_order = Order::new(2, false);
+
+ assert_eq!(asc_order.query(), "1 ASC".to_string());
+ assert_eq!(desc_order.query(), "2 DESC".to_string());
+ }
+
+ #[test]
+ fn test_generate_order_query() {
+ let mut order_manager = OrderManager::new();
+
+ // If orders is empty, it should return None.
+ assert_eq!(order_manager.generate_order_query(), None);
+
+ order_manager.add_order(1);
+ order_manager.add_order(1);
+ order_manager.add_order(2);
+ assert_eq!(
+ order_manager.generate_order_query(),
+ Some("ORDER BY 2 DESC, 3 ASC".to_string())
+ )
+ }
+
+ #[test]
+ fn test_generate_header_icons() {
+ let mut order_manager = OrderManager::new();
+ assert_eq!(order_manager.generate_header_icons(1), vec![String::new()]);
+
+ order_manager.add_order(1);
+ order_manager.add_order(1);
+ order_manager.add_order(2);
+ assert_eq!(
+ order_manager.generate_header_icons(3),
+ vec![String::new(), "↓1".to_string(), "↑2".to_string()]
+ );
+ assert_eq!(
+ order_manager.generate_header_icons(4),
+ vec![
+ String::new(),
+ "↓1".to_string(),
+ "↑2".to_string(),
+ String::new()
+ ]
+ );
+ }
+
+ #[test]
+ fn test_add_order() {
+ let mut order_manager = OrderManager::new();
+
+ // press first time, condition is asc.
+ order_manager.add_order(1);
+ assert_eq!(order_manager.orders, vec![Order::new(2, true)]);
+
+ // press twice times, condition is desc.
+ order_manager.add_order(1);
+ assert_eq!(order_manager.orders, vec![Order::new(2, false)]);
+
+ // press another column, this column is second order.
+ order_manager.add_order(2);
+ assert_eq!(
+ order_manager.orders,
+ vec![Order::new(2, false), Order::new(3, true)]
+ );
+
+ // press three times, removed.
+ order_manager.add_order(1);
+ assert_eq!(order_manager.orders, vec![Order::new(3, true)]);
+ }
+
#[test]
fn test_expand_selected_area_x_left() {
// before
From 7a32f3ac12d93b0d95ea442f5845e191ff078145 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:47:32 +0900
Subject: [PATCH 3/9] adding keymap for s
---
src/components/command.rs | 7 +++++++
src/config.rs | 2 ++
2 files changed, 9 insertions(+)
diff --git a/src/components/command.rs b/src/components/command.rs
index 961b8c6..bbe7425 100644
--- a/src/components/command.rs
+++ b/src/components/command.rs
@@ -62,6 +62,13 @@ pub fn scroll_to_top_bottom(key: &KeyConfig) -> CommandText {
)
}
+pub fn sort_by_column(key: &KeyConfig) -> CommandText {
+ CommandText::new(
+ format!("Sort by column [{}]", key.sort_by_column),
+ CMD_GROUP_DATABASES,
+ )
+}
+
pub fn expand_collapse(key: &KeyConfig) -> CommandText {
CommandText::new(
format!("Expand/Collapse [{},{}]", key.scroll_right, key.scroll_left,),
diff --git a/src/config.rs b/src/config.rs
index 3d41831..fad288f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -101,6 +101,7 @@ pub struct KeyConfig {
pub scroll_up_multiple_lines: Key,
pub scroll_to_top: Key,
pub scroll_to_bottom: Key,
+ pub sort_by_column: Key,
pub extend_selection_by_one_cell_left: Key,
pub extend_selection_by_one_cell_right: Key,
pub extend_selection_by_one_cell_up: Key,
@@ -140,6 +141,7 @@ impl Default for KeyConfig {
scroll_up_multiple_lines: Key::Ctrl('u'),
scroll_to_top: Key::Char('g'),
scroll_to_bottom: Key::Char('G'),
+ sort_by_column: Key::Char('s'),
extend_selection_by_one_cell_left: Key::Char('H'),
extend_selection_by_one_cell_right: Key::Char('L'),
extend_selection_by_one_cell_down: Key::Char('J'),
From f4e7877c6e7e22076e7eff419a9198683a49d0e8 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:48:01 +0900
Subject: [PATCH 4/9] impl for passing order and header_icons
---
src/database/mod.rs | 40 +++++++++++++++++++++++++
src/database/mysql.rs | 35 +++++++++++++++++-----
src/database/postgres.rs | 63 ++++++++++++++++++++++++++++++++++++----
src/database/sqlite.rs | 29 ++++++++++++++----
4 files changed, 149 insertions(+), 18 deletions(-)
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 46a771a..7cad19f 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -22,6 +22,8 @@ pub trait Pool: Send + Sync {
table: &Table,
page: u16,
filter: Option,
+ orders: Option,
+ header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)>;
async fn get_columns(
&self,
@@ -46,6 +48,20 @@ pub trait Pool: Send + Sync {
async fn close(&self);
}
+fn concat_headers(headers: Vec, header_icons: Option>) -> Vec {
+ if let Some(header_icons) = &header_icons {
+ let mut new_headers = vec![String::new(); headers.len()];
+ for (index, header) in headers.iter().enumerate() {
+ new_headers[index] = format!("{} {}", header, header_icons[index])
+ .trim()
+ .to_string();
+ }
+ return new_headers;
+ } else {
+ return headers;
+ }
+}
+
pub enum ExecuteResult {
Read {
headers: Vec,
@@ -69,3 +85,27 @@ macro_rules! get_or_null {
$value.map_or("NULL".to_string(), |v| v.to_string())
};
}
+
+#[cfg(test)]
+mod test {
+ use super::concat_headers;
+ #[test]
+ fn test_concat_headers() {
+ let headers = vec![
+ "ID".to_string(),
+ "NAME".to_string(),
+ "TIMESTAMP".to_string(),
+ ];
+ let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()];
+ let concat_headers: Vec = concat_headers(headers, Some(header_icons));
+
+ assert_eq!(
+ concat_headers,
+ vec![
+ "ID".to_string(),
+ "NAME ↑1".to_string(),
+ "TIMESTAMP ↓2".to_string()
+ ]
+ )
+ }
+}
diff --git a/src/database/mysql.rs b/src/database/mysql.rs
index 160306f..c810cdd 100644
--- a/src/database/mysql.rs
+++ b/src/database/mysql.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use database_tree::{Child, Database, Table};
@@ -228,21 +228,42 @@ impl Pool for MySqlPool {
table: &Table,
page: u16,
filter: Option,
+ orders: Option,
+ header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
- let query = if let Some(filter) = filter {
+ let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
+ format!(
+ "SELECT * FROM `{database}`.`{table}` WHERE {filter} {orders} LIMIT {page}, {limit}",
+ database = database.name,
+ table = table.name,
+ filter = filter,
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE,
+ orders = orders
+ )
+ } else if let Some(filter) = filter {
format!(
"SELECT * FROM `{database}`.`{table}` WHERE {filter} LIMIT {page}, {limit}",
database = database.name,
table = table.name,
filter = filter,
page = page,
- limit = RECORDS_LIMIT_PER_PAGE
+ limit = RECORDS_LIMIT_PER_PAGE,
+ )
+ } else if let Some(orders) = orders {
+ format!(
+ "SELECT * FROM `{database}`.`{table}` {orders} LIMIT {page}, {limit}",
+ database = database.name,
+ table = table.name,
+ orders = orders,
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE,
)
} else {
format!(
- "SELECT * FROM `{}`.`{}` LIMIT {page}, {limit}",
- database.name,
- table.name,
+ "SELECT * FROM `{database}`.`{table}` LIMIT {page}, {limit}",
+ database = database.name,
+ table = table.name,
page = page,
limit = RECORDS_LIMIT_PER_PAGE
)
@@ -262,7 +283,7 @@ impl Pool for MySqlPool {
}
records.push(new_row)
}
- Ok((headers, records))
+ Ok((concat_headers(headers, header_icons), records))
}
async fn get_columns(
diff --git a/src/database/postgres.rs b/src/database/postgres.rs
index ab954b0..78b6bc0 100644
--- a/src/database/postgres.rs
+++ b/src/database/postgres.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use database_tree::{Child, Database, Schema, Table};
@@ -245,8 +245,21 @@ impl Pool for PostgresPool {
table: &Table,
page: u16,
filter: Option,
+ orders: Option,
+ header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
- let query = if let Some(filter) = filter.as_ref() {
+ let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
+ format!(
+ r#"SELECT * FROM "{database}"."{table_schema}"."{table}" WHERE {filter} {orders} LIMIT {limit} OFFSET {page}"#,
+ database = database.name,
+ table = table.name,
+ filter = filter,
+ orders = orders,
+ table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()),
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE
+ )
+ } else if let Some(filter) = &filter {
format!(
r#"SELECT * FROM "{database}"."{table_schema}"."{table}" WHERE {filter} LIMIT {limit} OFFSET {page}"#,
database = database.name,
@@ -256,6 +269,16 @@ impl Pool for PostgresPool {
page = page,
limit = RECORDS_LIMIT_PER_PAGE
)
+ } else if let Some(orders) = &orders {
+ format!(
+ r#"SELECT * FROM "{database}"."{table_schema}"."{table}" {orders} LIMIT {limit} OFFSET {page}"#,
+ database = database.name,
+ table = table.name,
+ orders = orders,
+ table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()),
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE
+ )
} else {
format!(
r#"SELECT * FROM "{database}"."{table_schema}"."{table}" LIMIT {limit} OFFSET {page}"#,
@@ -283,8 +306,14 @@ impl Pool for PostgresPool {
Err(_) => {
if json_records.is_none() {
json_records = Some(
- self.get_json_records(database, table, page, filter.clone())
- .await?,
+ self.get_json_records(
+ database,
+ table,
+ page,
+ filter.clone(),
+ orders.clone(),
+ )
+ .await?,
);
}
if let Some(json_records) = &json_records {
@@ -315,7 +344,7 @@ impl Pool for PostgresPool {
}
records.push(new_row)
}
- Ok((headers, records))
+ Ok((concat_headers(headers, header_icons), records))
}
async fn get_columns(
@@ -479,8 +508,20 @@ impl PostgresPool {
table: &Table,
page: u16,
filter: Option,
+ orders: Option,
) -> anyhow::Result> {
- let query = if let Some(filter) = filter {
+ let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
+ format!(
+ r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" WHERE {filter} {orders} LIMIT {limit} OFFSET {page}"#,
+ database = database.name,
+ table = table.name,
+ filter = filter,
+ orders = orders,
+ table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()),
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE
+ )
+ } else if let Some(filter) = filter {
format!(
r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" WHERE {filter} LIMIT {limit} OFFSET {page}"#,
database = database.name,
@@ -490,6 +531,16 @@ impl PostgresPool {
page = page,
limit = RECORDS_LIMIT_PER_PAGE
)
+ } else if let Some(orders) = orders {
+ format!(
+ r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" {orders} LIMIT {limit} OFFSET {page}"#,
+ database = database.name,
+ table = table.name,
+ orders = orders,
+ table_schema = table.schema.clone().unwrap_or_else(|| "public".to_string()),
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE
+ )
} else {
format!(
r#"SELECT to_json("{table}".*) FROM "{database}"."{table_schema}"."{table}" LIMIT {limit} OFFSET {page}"#,
diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs
index 83063c7..166f069 100644
--- a/src/database/sqlite.rs
+++ b/src/database/sqlite.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::NaiveDateTime;
use database_tree::{Child, Database, Table};
@@ -230,19 +230,38 @@ impl Pool for SqlitePool {
table: &Table,
page: u16,
filter: Option,
+ orders: Option,
+ header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
- let query = if let Some(filter) = filter {
+ let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
+ format!(
+ "SELECT * FROM `{table}` WHERE {filter} {orders} LIMIT {page}, {limit}",
+ table = table.name,
+ filter = filter,
+ page = page,
+ limit = RECORDS_LIMIT_PER_PAGE,
+ orders = orders
+ )
+ } else if let Some(filter) = filter {
format!(
"SELECT * FROM `{table}` WHERE {filter} LIMIT {page}, {limit}",
table = table.name,
filter = filter,
page = page,
+ limit = RECORDS_LIMIT_PER_PAGE,
+ )
+ } else if let Some(orders) = orders {
+ format!(
+ "SELECT * FROM `{table}`{orders} LIMIT {page}, {limit}",
+ table = table.name,
+ orders = orders,
+ page = page,
limit = RECORDS_LIMIT_PER_PAGE
)
} else {
format!(
- "SELECT * FROM `{}` LIMIT {page}, {limit}",
- table.name,
+ "SELECT * FROM `{table}` LIMIT {page}, {limit}",
+ table = table.name,
page = page,
limit = RECORDS_LIMIT_PER_PAGE
)
@@ -262,7 +281,7 @@ impl Pool for SqlitePool {
}
records.push(new_row)
}
- Ok((headers, records))
+ Ok((concat_headers(headers, header_icons), records))
}
async fn get_columns(
From 24756655c3c54f23b9d7d7fde44f67d81f1b9984 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:49:48 +0900
Subject: [PATCH 5/9] Add processing when the key is pressed
---
src/app.rs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/app.rs b/src/app.rs
index abfc7b1..2f3968b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -249,6 +249,17 @@ impl App {
return Ok(EventState::Consumed);
};
+ if key == self.config.key_config.sort_by_column
+ && !self.record_table.table.headers.is_empty()
+ {
+ self.record_table.table.add_order();
+ let order_query = self.record_table.table.generate_order_query();
+ let header_icons = self.record_table.table.generate_header_icons();
+ self.update_record_table(order_query, Some(header_icons), true)
+ .await?;
+ return Ok(EventState::Consumed);
+ };
+
if key == self.config.key_config.copy {
if let Some(text) = self.record_table.table.selected_cells() {
copy_to_clipboard(text.as_str())?
@@ -258,7 +269,10 @@ impl App {
if key == self.config.key_config.enter && self.record_table.filter_focused()
{
self.record_table.focus = crate::components::record_table::Focus::Table;
- self.update_record_table().await?;
+ let order_query = self.record_table.table.generate_order_query();
+ let header_icons = self.record_table.table.generate_header_icons();
+ self.update_record_table(order_query, Some(header_icons), false)
+ .await?;
}
if self.record_table.table.eod {
From 405de596afd6d6d93b7d9460c799611dc7e75620 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:50:19 +0900
Subject: [PATCH 6/9] adding args for passing order_query and header_icons
---
src/app.rs | 31 +++++++++++++++++++++++++------
src/components/properties.rs | 4 ++++
src/components/record_table.rs | 4 +++-
src/components/sql_editor.rs | 2 +-
4 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 2f3968b..2cab8d8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -162,7 +162,12 @@ impl App {
Ok(())
}
- async fn update_record_table(&mut self) -> anyhow::Result<()> {
+ async fn update_record_table(
+ &mut self,
+ orders: Option,
+ header_icons: Option>,
+ hold_cursor_position: bool,
+ ) -> anyhow::Result<()> {
if let Some((database, table)) = self.databases.tree().selected_table() {
let (headers, records) = self
.pool
@@ -177,10 +182,17 @@ impl App {
} else {
Some(self.record_table.filter.input_str())
},
+ orders,
+ header_icons,
)
.await?;
- self.record_table
- .update(records, headers, database.clone(), table.clone());
+ self.record_table.update(
+ records,
+ headers,
+ database.clone(),
+ table.clone(),
+ hold_cursor_position,
+ );
}
Ok(())
}
@@ -230,10 +242,15 @@ impl App {
.pool
.as_ref()
.unwrap()
- .get_records(&database, &table, 0, None)
+ .get_records(&database, &table, 0, None, None, None)
.await?;
- self.record_table
- .update(records, headers, database.clone(), table.clone());
+ self.record_table.update(
+ records,
+ headers,
+ database.clone(),
+ table.clone(),
+ false,
+ );
self.properties
.update(database.clone(), table.clone(), self.pool.as_ref().unwrap())
.await?;
@@ -297,6 +314,8 @@ impl App {
} else {
Some(self.record_table.filter.input_str())
},
+ None,
+ None,
)
.await?;
if !records.is_empty() {
diff --git a/src/components/properties.rs b/src/components/properties.rs
index acd6ee4..4e42c18 100644
--- a/src/components/properties.rs
+++ b/src/components/properties.rs
@@ -77,6 +77,7 @@ impl PropertiesComponent {
columns.get(0).unwrap().fields(),
database.clone(),
table.clone(),
+ false,
);
}
self.constraint_table.reset();
@@ -90,6 +91,7 @@ impl PropertiesComponent {
constraints.get(0).unwrap().fields(),
database.clone(),
table.clone(),
+ false,
);
}
self.foreign_key_table.reset();
@@ -103,6 +105,7 @@ impl PropertiesComponent {
foreign_keys.get(0).unwrap().fields(),
database.clone(),
table.clone(),
+ false,
);
}
self.index_table.reset();
@@ -116,6 +119,7 @@ impl PropertiesComponent {
indexes.get(0).unwrap().fields(),
database.clone(),
table.clone(),
+ false,
);
}
Ok(())
diff --git a/src/components/record_table.rs b/src/components/record_table.rs
index d4857aa..2f5c84f 100644
--- a/src/components/record_table.rs
+++ b/src/components/record_table.rs
@@ -39,8 +39,10 @@ impl RecordTableComponent {
headers: Vec,
database: Database,
table: DTable,
+ hold_cusor_position: bool,
) {
- self.table.update(rows, headers, database, table.clone());
+ self.table
+ .update(rows, headers, database, table.clone(), hold_cusor_position);
self.filter.table = Some(table);
}
diff --git a/src/components/sql_editor.rs b/src/components/sql_editor.rs
index 50e53cf..b8ad596 100644
--- a/src/components/sql_editor.rs
+++ b/src/components/sql_editor.rs
@@ -269,7 +269,7 @@ impl Component for SqlEditorComponent {
database,
table,
} => {
- self.table.update(rows, headers, database, table);
+ self.table.update(rows, headers, database, table, false);
self.focus = Focus::Table;
self.query_result = None;
}
From 75bcf73cd63d6a6219a231708f9150fc62590001 Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 12:36:12 +0900
Subject: [PATCH 7/9] remove needless return
---
src/components/table.rs | 11 ++++++-----
src/database/mod.rs | 4 ++--
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/components/table.rs b/src/components/table.rs
index 7efc5be..09b5595 100644
--- a/src/components/table.rs
+++ b/src/components/table.rs
@@ -61,9 +61,9 @@ impl OrderManager {
if !order_query.is_empty() {
return Some("ORDER BY ".to_string() + &order_query.join(", "));
- } else {
- return None;
}
+
+ None
}
fn generate_header_icons(&mut self, header_length: usize) -> Vec {
@@ -74,7 +74,8 @@ impl OrderManager {
header_icons[order.column_number - 1] =
format!("{arrow}{number}", arrow = arrow, number = index + 1);
}
- return header_icons;
+
+ header_icons
}
fn add_order(&mut self, selected_column: usize) {
@@ -181,11 +182,11 @@ impl TableComponent {
}
pub fn generate_order_query(&mut self) -> Option {
- return self.orders.generate_order_query();
+ self.orders.generate_order_query()
}
pub fn generate_header_icons(&mut self) -> Vec {
- return self.orders.generate_header_icons(self.headers.len());
+ self.orders.generate_header_icons(self.headers.len())
}
pub fn end(&mut self) {
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 7cad19f..059fb3b 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -57,9 +57,9 @@ fn concat_headers(headers: Vec, header_icons: Option>) -> Ve
.to_string();
}
return new_headers;
- } else {
- return headers;
}
+
+ headers
}
pub enum ExecuteResult {
From 35a2ae7d18e7d855eaadbc938a616acf21e2f40c Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Mon, 11 Apr 2022 13:46:19 +0900
Subject: [PATCH 8/9] refactor concat_headers
concat_headers method moves to App
---
src/app.rs | 45 ++++++++++++++++++++++++++++++++++++----
src/database/mod.rs | 39 ----------------------------------
src/database/mysql.rs | 5 ++---
src/database/postgres.rs | 5 ++---
src/database/sqlite.rs | 5 ++---
5 files changed, 47 insertions(+), 52 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 2cab8d8..1dcab65 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -183,12 +183,11 @@ impl App {
Some(self.record_table.filter.input_str())
},
orders,
- header_icons,
)
.await?;
self.record_table.update(
records,
- headers,
+ self.concat_headers(headers, header_icons),
database.clone(),
table.clone(),
hold_cursor_position,
@@ -242,7 +241,7 @@ impl App {
.pool
.as_ref()
.unwrap()
- .get_records(&database, &table, 0, None, None, None)
+ .get_records(&database, &table, 0, None, None)
.await?;
self.record_table.update(
records,
@@ -315,7 +314,6 @@ impl App {
Some(self.record_table.filter.input_str())
},
None,
- None,
)
.await?;
if !records.is_empty() {
@@ -406,6 +404,24 @@ impl App {
}
Ok(EventState::NotConsumed)
}
+
+ fn concat_headers(
+ &self,
+ headers: Vec,
+ header_icons: Option>,
+ ) -> Vec {
+ if let Some(header_icons) = &header_icons {
+ let mut new_headers = vec![String::new(); headers.len()];
+ for (index, header) in headers.iter().enumerate() {
+ new_headers[index] = format!("{} {}", header, header_icons[index])
+ .trim()
+ .to_string();
+ }
+ return new_headers;
+ }
+
+ headers
+ }
}
#[cfg(test)]
@@ -441,4 +457,25 @@ mod test {
);
assert_eq!(app.left_main_chunk_percentage, 15);
}
+
+ #[test]
+ fn test_concat_headers() {
+ let app = App::new(Config::default());
+ let headers = vec![
+ "ID".to_string(),
+ "NAME".to_string(),
+ "TIMESTAMP".to_string(),
+ ];
+ let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()];
+ let concat_headers: Vec = app.concat_headers(headers, Some(header_icons));
+
+ assert_eq!(
+ concat_headers,
+ vec![
+ "ID".to_string(),
+ "NAME ↑1".to_string(),
+ "TIMESTAMP ↓2".to_string()
+ ]
+ )
+ }
}
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 059fb3b..76a874a 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -23,7 +23,6 @@ pub trait Pool: Send + Sync {
page: u16,
filter: Option,
orders: Option,
- header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)>;
async fn get_columns(
&self,
@@ -48,20 +47,6 @@ pub trait Pool: Send + Sync {
async fn close(&self);
}
-fn concat_headers(headers: Vec, header_icons: Option>) -> Vec {
- if let Some(header_icons) = &header_icons {
- let mut new_headers = vec![String::new(); headers.len()];
- for (index, header) in headers.iter().enumerate() {
- new_headers[index] = format!("{} {}", header, header_icons[index])
- .trim()
- .to_string();
- }
- return new_headers;
- }
-
- headers
-}
-
pub enum ExecuteResult {
Read {
headers: Vec,
@@ -85,27 +70,3 @@ macro_rules! get_or_null {
$value.map_or("NULL".to_string(), |v| v.to_string())
};
}
-
-#[cfg(test)]
-mod test {
- use super::concat_headers;
- #[test]
- fn test_concat_headers() {
- let headers = vec![
- "ID".to_string(),
- "NAME".to_string(),
- "TIMESTAMP".to_string(),
- ];
- let header_icons = vec!["".to_string(), "↑1".to_string(), "↓2".to_string()];
- let concat_headers: Vec = concat_headers(headers, Some(header_icons));
-
- assert_eq!(
- concat_headers,
- vec![
- "ID".to_string(),
- "NAME ↑1".to_string(),
- "TIMESTAMP ↓2".to_string()
- ]
- )
- }
-}
diff --git a/src/database/mysql.rs b/src/database/mysql.rs
index c810cdd..1f70f73 100644
--- a/src/database/mysql.rs
+++ b/src/database/mysql.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use database_tree::{Child, Database, Table};
@@ -229,7 +229,6 @@ impl Pool for MySqlPool {
page: u16,
filter: Option,
orders: Option,
- header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
format!(
@@ -283,7 +282,7 @@ impl Pool for MySqlPool {
}
records.push(new_row)
}
- Ok((concat_headers(headers, header_icons), records))
+ Ok((headers, records))
}
async fn get_columns(
diff --git a/src/database/postgres.rs b/src/database/postgres.rs
index 78b6bc0..f7493a0 100644
--- a/src/database/postgres.rs
+++ b/src/database/postgres.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use database_tree::{Child, Database, Schema, Table};
@@ -246,7 +246,6 @@ impl Pool for PostgresPool {
page: u16,
filter: Option,
orders: Option,
- header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
format!(
@@ -344,7 +343,7 @@ impl Pool for PostgresPool {
}
records.push(new_row)
}
- Ok((concat_headers(headers, header_icons), records))
+ Ok((headers, records))
}
async fn get_columns(
diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs
index 166f069..5a0d5f4 100644
--- a/src/database/sqlite.rs
+++ b/src/database/sqlite.rs
@@ -1,6 +1,6 @@
use crate::get_or_null;
-use super::{concat_headers, ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
+use super::{ExecuteResult, Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
use async_trait::async_trait;
use chrono::NaiveDateTime;
use database_tree::{Child, Database, Table};
@@ -231,7 +231,6 @@ impl Pool for SqlitePool {
page: u16,
filter: Option,
orders: Option,
- header_icons: Option>,
) -> anyhow::Result<(Vec, Vec>)> {
let query = if let (Some(filter), Some(orders)) = (&filter, &orders) {
format!(
@@ -281,7 +280,7 @@ impl Pool for SqlitePool {
}
records.push(new_row)
}
- Ok((concat_headers(headers, header_icons), records))
+ Ok((headers, records))
}
async fn get_columns(
From 1fc730e863ba135444ae2ae0c254921f8b4c74ff Mon Sep 17 00:00:00 2001
From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com>
Date: Thu, 14 Apr 2022 13:28:43 +0900
Subject: [PATCH 9/9] fix CommandText TYPE, because it mistaked
---
src/components/command.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/command.rs b/src/components/command.rs
index bbe7425..1e8f9ce 100644
--- a/src/components/command.rs
+++ b/src/components/command.rs
@@ -65,7 +65,7 @@ pub fn scroll_to_top_bottom(key: &KeyConfig) -> CommandText {
pub fn sort_by_column(key: &KeyConfig) -> CommandText {
CommandText::new(
format!("Sort by column [{}]", key.sort_by_column),
- CMD_GROUP_DATABASES,
+ CMD_GROUP_TABLE,
)
}