mirror of
https://github.com/TaKO8Ki/gobang
synced 2024-10-31 03:20:33 +00:00
Add constraints tab (#41)
* get constraints * replace / with . * add constraint tab * return TableRow * remove comment column * fix clippy warnings
This commit is contained in:
parent
99961aea72
commit
2a0abf65cb
67
src/app.rs
67
src/app.rs
@ -24,7 +24,8 @@ pub enum Focus {
|
|||||||
}
|
}
|
||||||
pub struct App {
|
pub struct App {
|
||||||
record_table: RecordTableComponent,
|
record_table: RecordTableComponent,
|
||||||
structure_table: TableComponent,
|
column_table: TableComponent,
|
||||||
|
constraint_table: TableComponent,
|
||||||
focus: Focus,
|
focus: Focus,
|
||||||
tab: TabComponent,
|
tab: TabComponent,
|
||||||
help: HelpComponent,
|
help: HelpComponent,
|
||||||
@ -42,7 +43,8 @@ impl App {
|
|||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
connections: ConnectionsComponent::new(config.key_config.clone(), config.conn),
|
connections: ConnectionsComponent::new(config.key_config.clone(), config.conn),
|
||||||
record_table: RecordTableComponent::new(config.key_config.clone()),
|
record_table: RecordTableComponent::new(config.key_config.clone()),
|
||||||
structure_table: TableComponent::new(config.key_config.clone()),
|
column_table: TableComponent::new(config.key_config.clone()),
|
||||||
|
constraint_table: TableComponent::new(config.key_config.clone()),
|
||||||
tab: TabComponent::new(config.key_config.clone()),
|
tab: TabComponent::new(config.key_config.clone()),
|
||||||
help: HelpComponent::new(config.key_config.clone()),
|
help: HelpComponent::new(config.key_config.clone()),
|
||||||
databases: DatabasesComponent::new(config.key_config.clone()),
|
databases: DatabasesComponent::new(config.key_config.clone()),
|
||||||
@ -93,10 +95,15 @@ impl App {
|
|||||||
self.record_table
|
self.record_table
|
||||||
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
|
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
|
||||||
}
|
}
|
||||||
Tab::Structure => {
|
Tab::Columns => {
|
||||||
self.structure_table
|
self.column_table
|
||||||
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
|
.draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?
|
||||||
}
|
}
|
||||||
|
Tab::Constraints => self.constraint_table.draw(
|
||||||
|
f,
|
||||||
|
right_chunks[1],
|
||||||
|
matches!(self.focus, Focus::Table),
|
||||||
|
)?,
|
||||||
}
|
}
|
||||||
self.error.draw(f, Rect::default(), false)?;
|
self.error.draw(f, Rect::default(), false)?;
|
||||||
self.help.draw(f, Rect::default(), false)?;
|
self.help.draw(f, Rect::default(), false)?;
|
||||||
@ -162,6 +169,7 @@ impl App {
|
|||||||
async fn update_table(&mut self) -> anyhow::Result<()> {
|
async fn update_table(&mut self) -> anyhow::Result<()> {
|
||||||
if let Some((database, table)) = self.databases.tree().selected_table() {
|
if let Some((database, table)) = self.databases.tree().selected_table() {
|
||||||
self.focus = Focus::Table;
|
self.focus = Focus::Table;
|
||||||
|
self.record_table.reset();
|
||||||
let (headers, records) = self
|
let (headers, records) = self
|
||||||
.pool
|
.pool
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -171,14 +179,42 @@ impl App {
|
|||||||
self.record_table
|
self.record_table
|
||||||
.update(records, headers, database.clone(), table.clone());
|
.update(records, headers, database.clone(), table.clone());
|
||||||
|
|
||||||
let (headers, records) = self
|
self.column_table.reset();
|
||||||
|
let columns = self
|
||||||
.pool
|
.pool
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_columns(&database, &table)
|
.get_columns(&database, &table)
|
||||||
.await?;
|
.await?;
|
||||||
self.structure_table
|
if !columns.is_empty() {
|
||||||
.update(records, headers, database.clone(), table.clone());
|
self.column_table.update(
|
||||||
|
columns
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.columns())
|
||||||
|
.collect::<Vec<Vec<String>>>(),
|
||||||
|
columns.get(0).unwrap().fields(),
|
||||||
|
database.clone(),
|
||||||
|
table.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.constraint_table.reset();
|
||||||
|
let constraints = self
|
||||||
|
.pool
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get_constraints(&database, &table)
|
||||||
|
.await?;
|
||||||
|
if !constraints.is_empty() {
|
||||||
|
self.constraint_table.update(
|
||||||
|
constraints
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.columns())
|
||||||
|
.collect::<Vec<Vec<String>>>(),
|
||||||
|
constraints.get(0).unwrap().fields(),
|
||||||
|
database.clone(),
|
||||||
|
table.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
self.table_status
|
self.table_status
|
||||||
.update(self.record_table.len() as u64, table);
|
.update(self.record_table.len() as u64, table);
|
||||||
}
|
}
|
||||||
@ -303,13 +339,24 @@ impl App {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Tab::Structure => {
|
Tab::Columns => {
|
||||||
if self.structure_table.event(key)?.is_consumed() {
|
if self.column_table.event(key)?.is_consumed() {
|
||||||
return Ok(EventState::Consumed);
|
return Ok(EventState::Consumed);
|
||||||
};
|
};
|
||||||
|
|
||||||
if key == self.config.key_config.copy {
|
if key == self.config.key_config.copy {
|
||||||
if let Some(text) = self.structure_table.selected_cells() {
|
if let Some(text) = self.column_table.selected_cells() {
|
||||||
|
copy_to_clipboard(text.as_str())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Tab::Constraints => {
|
||||||
|
if self.constraint_table.event(key)?.is_consumed() {
|
||||||
|
return Ok(EventState::Consumed);
|
||||||
|
};
|
||||||
|
|
||||||
|
if key == self.config.key_config.copy {
|
||||||
|
if let Some(text) = self.column_table.selected_cells() {
|
||||||
copy_to_clipboard(text.as_str())?
|
copy_to_clipboard(text.as_str())?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -99,9 +99,13 @@ pub fn tab_records(key: &KeyConfig) -> CommandText {
|
|||||||
CommandText::new(format!("Records [{}]", key.tab_records), CMD_GROUP_TABLE)
|
CommandText::new(format!("Records [{}]", key.tab_records), CMD_GROUP_TABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_structure(key: &KeyConfig) -> CommandText {
|
pub fn tab_columns(key: &KeyConfig) -> CommandText {
|
||||||
|
CommandText::new(format!("Columns [{}]", key.tab_columns), CMD_GROUP_TABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tab_constraints(key: &KeyConfig) -> CommandText {
|
||||||
CommandText::new(
|
CommandText::new(
|
||||||
format!("Structure [{}]", key.tab_structure),
|
format!("Constraints [{}]", key.tab_constraints),
|
||||||
CMD_GROUP_TABLE,
|
CMD_GROUP_TABLE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -109,8 +113,8 @@ pub fn tab_structure(key: &KeyConfig) -> CommandText {
|
|||||||
pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText {
|
pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText {
|
||||||
CommandText::new(
|
CommandText::new(
|
||||||
format!(
|
format!(
|
||||||
"Tab [{},{}]",
|
"Tab [{},{},{}]",
|
||||||
key_config.tab_records, key_config.tab_structure
|
key_config.tab_records, key_config.tab_columns, key_config.tab_constraints
|
||||||
),
|
),
|
||||||
CMD_GROUP_GENERAL,
|
CMD_GROUP_GENERAL,
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,8 @@ use tui::{
|
|||||||
#[derive(Debug, Clone, Copy, EnumIter)]
|
#[derive(Debug, Clone, Copy, EnumIter)]
|
||||||
pub enum Tab {
|
pub enum Tab {
|
||||||
Records,
|
Records,
|
||||||
Structure,
|
Columns,
|
||||||
|
Constraints,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Tab {
|
impl std::fmt::Display for Tab {
|
||||||
@ -45,7 +46,8 @@ impl TabComponent {
|
|||||||
fn names(&self) -> Vec<String> {
|
fn names(&self) -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
command::tab_records(&self.key_config).name,
|
command::tab_records(&self.key_config).name,
|
||||||
command::tab_structure(&self.key_config).name,
|
command::tab_columns(&self.key_config).name,
|
||||||
|
command::tab_constraints(&self.key_config).name,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,8 +76,11 @@ impl Component for TabComponent {
|
|||||||
if key == self.key_config.tab_records {
|
if key == self.key_config.tab_records {
|
||||||
self.selected_tab = Tab::Records;
|
self.selected_tab = Tab::Records;
|
||||||
return Ok(EventState::Consumed);
|
return Ok(EventState::Consumed);
|
||||||
} else if key == self.key_config.tab_structure {
|
} else if key == self.key_config.tab_columns {
|
||||||
self.selected_tab = Tab::Structure;
|
self.selected_tab = Tab::Columns;
|
||||||
|
return Ok(EventState::Consumed);
|
||||||
|
} else if key == self.key_config.tab_constraints {
|
||||||
|
self.selected_tab = Tab::Constraints;
|
||||||
return Ok(EventState::Consumed);
|
return Ok(EventState::Consumed);
|
||||||
}
|
}
|
||||||
Ok(EventState::NotConsumed)
|
Ok(EventState::NotConsumed)
|
||||||
|
@ -48,7 +48,7 @@ impl TableComponent {
|
|||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
self.table.as_ref().map_or(" - ".to_string(), |table| {
|
self.table.as_ref().map_or(" - ".to_string(), |table| {
|
||||||
format!("{}/{}", table.0.name, table.1.name)
|
format!("{}.{}", table.0.name, table.1.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,8 @@ pub struct KeyConfig {
|
|||||||
pub extend_selection_by_one_cell_up: Key,
|
pub extend_selection_by_one_cell_up: Key,
|
||||||
pub extend_selection_by_one_cell_down: Key,
|
pub extend_selection_by_one_cell_down: Key,
|
||||||
pub tab_records: Key,
|
pub tab_records: Key,
|
||||||
pub tab_structure: Key,
|
pub tab_columns: Key,
|
||||||
|
pub tab_constraints: Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeyConfig {
|
impl Default for KeyConfig {
|
||||||
@ -108,7 +109,8 @@ impl Default for KeyConfig {
|
|||||||
extend_selection_by_one_cell_down: Key::Char('J'),
|
extend_selection_by_one_cell_down: Key::Char('J'),
|
||||||
extend_selection_by_one_cell_up: Key::Char('K'),
|
extend_selection_by_one_cell_up: Key::Char('K'),
|
||||||
tab_records: Key::Char('1'),
|
tab_records: Key::Char('1'),
|
||||||
tab_structure: Key::Char('2'),
|
tab_columns: Key::Char('2'),
|
||||||
|
tab_constraints: Key::Char('3'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,16 @@ pub trait Pool {
|
|||||||
&self,
|
&self,
|
||||||
database: &Database,
|
database: &Database,
|
||||||
table: &Table,
|
table: &Table,
|
||||||
) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)>;
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>>;
|
||||||
|
async fn get_constraints(
|
||||||
|
&self,
|
||||||
|
database: &Database,
|
||||||
|
table: &Table,
|
||||||
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>>;
|
||||||
async fn close(&self);
|
async fn close(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TableRow: std::marker::Send {
|
||||||
|
fn fields(&self) -> Vec<String>;
|
||||||
|
fn columns(&self) -> Vec<String>;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{Pool, RECORDS_LIMIT_PER_PAGE};
|
use super::{Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use database_tree::{Child, Database, Table};
|
use database_tree::{Child, Database, Table};
|
||||||
@ -18,6 +18,61 @@ impl MySqlPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Constraint {
|
||||||
|
name: String,
|
||||||
|
column_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRow for Constraint {
|
||||||
|
fn fields(&self) -> Vec<String> {
|
||||||
|
vec!["name".to_string(), "column_name".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn columns(&self) -> Vec<String> {
|
||||||
|
vec![self.name.to_string(), self.column_name.to_string()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Column {
|
||||||
|
name: Option<String>,
|
||||||
|
r#type: Option<String>,
|
||||||
|
null: Option<String>,
|
||||||
|
default: Option<String>,
|
||||||
|
comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRow for Column {
|
||||||
|
fn fields(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
"name".to_string(),
|
||||||
|
"type".to_string(),
|
||||||
|
"null".to_string(),
|
||||||
|
"default".to_string(),
|
||||||
|
"comment".to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn columns(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
self.name
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |name| name.to_string()),
|
||||||
|
self.r#type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||||
|
self.null
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |null| null.to_string()),
|
||||||
|
self.default
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |default| default.to_string()),
|
||||||
|
self.comment
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |comment| comment.to_string()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Pool for MySqlPool {
|
impl Pool for MySqlPool {
|
||||||
async fn get_databases(&self) -> anyhow::Result<Vec<Database>> {
|
async fn get_databases(&self) -> anyhow::Result<Vec<Database>> {
|
||||||
@ -92,27 +147,53 @@ impl Pool for MySqlPool {
|
|||||||
&self,
|
&self,
|
||||||
database: &Database,
|
database: &Database,
|
||||||
table: &Table,
|
table: &Table,
|
||||||
) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)> {
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>> {
|
||||||
let query = format!(
|
let query = format!(
|
||||||
"SHOW FULL COLUMNS FROM `{}`.`{}`",
|
"SHOW FULL COLUMNS FROM `{}`.`{}`",
|
||||||
database.name, table.name
|
database.name, table.name
|
||||||
);
|
);
|
||||||
let mut rows = sqlx::query(query.as_str()).fetch(&self.pool);
|
let mut rows = sqlx::query(query.as_str()).fetch(&self.pool);
|
||||||
let mut headers = vec![];
|
let mut columns: Vec<Box<dyn TableRow>> = vec![];
|
||||||
let mut records = vec![];
|
|
||||||
while let Some(row) = rows.try_next().await? {
|
while let Some(row) = rows.try_next().await? {
|
||||||
headers = row
|
columns.push(Box::new(Column {
|
||||||
.columns()
|
name: row.try_get("Field")?,
|
||||||
.iter()
|
r#type: row.try_get("Type")?,
|
||||||
.map(|column| column.name().to_string())
|
null: row.try_get("Null")?,
|
||||||
.collect();
|
default: row.try_get("Default")?,
|
||||||
let mut new_row = vec![];
|
comment: row.try_get("Comment")?,
|
||||||
for column in row.columns() {
|
}))
|
||||||
new_row.push(convert_column_value_to_string(&row, column)?)
|
|
||||||
}
|
|
||||||
records.push(new_row)
|
|
||||||
}
|
}
|
||||||
Ok((headers, records))
|
Ok(columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_constraints(
|
||||||
|
&self,
|
||||||
|
database: &Database,
|
||||||
|
table: &Table,
|
||||||
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>> {
|
||||||
|
let mut rows = sqlx::query(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
COLUMN_NAME,
|
||||||
|
CONSTRAINT_NAME
|
||||||
|
FROM
|
||||||
|
information_schema.KEY_COLUMN_USAGE
|
||||||
|
WHERE
|
||||||
|
TABLE_SCHEMA = ?
|
||||||
|
AND TABLE_NAME = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(&database.name)
|
||||||
|
.bind(&table.name)
|
||||||
|
.fetch(&self.pool);
|
||||||
|
let mut constraints: Vec<Box<dyn TableRow>> = vec![];
|
||||||
|
while let Some(row) = rows.try_next().await? {
|
||||||
|
constraints.push(Box::new(Constraint {
|
||||||
|
name: row.try_get("CONSTRAINT_NAME")?,
|
||||||
|
column_name: row.try_get("COLUMN_NAME")?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Ok(constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn close(&self) {
|
async fn close(&self) {
|
||||||
@ -124,7 +205,7 @@ fn convert_column_value_to_string(row: &MySqlRow, column: &MySqlColumn) -> anyho
|
|||||||
let column_name = column.name();
|
let column_name = column.name();
|
||||||
if let Ok(value) = row.try_get(column_name) {
|
if let Ok(value) = row.try_get(column_name) {
|
||||||
let value: Option<String> = value;
|
let value: Option<String> = value;
|
||||||
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
|
return Ok(value.unwrap_or_else(|| "NULL".to_string()));
|
||||||
}
|
}
|
||||||
if let Ok(value) = row.try_get(column_name) {
|
if let Ok(value) = row.try_get(column_name) {
|
||||||
let value: Option<&str> = value;
|
let value: Option<&str> = value;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{Pool, RECORDS_LIMIT_PER_PAGE};
|
use super::{Pool, TableRow, RECORDS_LIMIT_PER_PAGE};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use database_tree::{Child, Database, Schema, Table};
|
use database_tree::{Child, Database, Schema, Table};
|
||||||
@ -19,6 +19,61 @@ impl PostgresPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Constraint {
|
||||||
|
name: String,
|
||||||
|
column_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRow for Constraint {
|
||||||
|
fn fields(&self) -> Vec<String> {
|
||||||
|
vec!["name".to_string(), "column_name".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn columns(&self) -> Vec<String> {
|
||||||
|
vec![self.name.to_string(), self.column_name.to_string()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Column {
|
||||||
|
name: Option<String>,
|
||||||
|
r#type: Option<String>,
|
||||||
|
null: Option<String>,
|
||||||
|
default: Option<String>,
|
||||||
|
comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRow for Column {
|
||||||
|
fn fields(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
"name".to_string(),
|
||||||
|
"type".to_string(),
|
||||||
|
"null".to_string(),
|
||||||
|
"default".to_string(),
|
||||||
|
"comment".to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn columns(&self) -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
self.name
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |name| name.to_string()),
|
||||||
|
self.r#type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||||
|
self.null
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |null| null.to_string()),
|
||||||
|
self.default
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |default| default.to_string()),
|
||||||
|
self.comment
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |comment| comment.to_string()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Pool for PostgresPool {
|
impl Pool for PostgresPool {
|
||||||
async fn get_databases(&self) -> anyhow::Result<Vec<Database>> {
|
async fn get_databases(&self) -> anyhow::Result<Vec<Database>> {
|
||||||
@ -46,11 +101,11 @@ impl Pool for PostgresPool {
|
|||||||
let mut tables = Vec::new();
|
let mut tables = Vec::new();
|
||||||
while let Some(row) = rows.try_next().await? {
|
while let Some(row) = rows.try_next().await? {
|
||||||
tables.push(Table {
|
tables.push(Table {
|
||||||
name: row.get("table_name"),
|
name: row.try_get("table_name")?,
|
||||||
create_time: None,
|
create_time: None,
|
||||||
update_time: None,
|
update_time: None,
|
||||||
engine: None,
|
engine: None,
|
||||||
schema: row.get("table_schema"),
|
schema: row.try_get("table_schema")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let mut schemas = vec![];
|
let mut schemas = vec![];
|
||||||
@ -147,7 +202,7 @@ impl Pool for PostgresPool {
|
|||||||
&self,
|
&self,
|
||||||
database: &Database,
|
database: &Database,
|
||||||
table: &Table,
|
table: &Table,
|
||||||
) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)> {
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>> {
|
||||||
let table_schema = table
|
let table_schema = table
|
||||||
.schema
|
.schema
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -157,21 +212,49 @@ impl Pool for PostgresPool {
|
|||||||
)
|
)
|
||||||
.bind(&database.name).bind(table_schema).bind(&table.name)
|
.bind(&database.name).bind(table_schema).bind(&table.name)
|
||||||
.fetch(&self.pool);
|
.fetch(&self.pool);
|
||||||
let mut headers = vec![];
|
let mut columns: Vec<Box<dyn TableRow>> = vec![];
|
||||||
let mut records = vec![];
|
|
||||||
while let Some(row) = rows.try_next().await? {
|
while let Some(row) = rows.try_next().await? {
|
||||||
headers = row
|
columns.push(Box::new(Column {
|
||||||
.columns()
|
name: row.try_get("column_name")?,
|
||||||
.iter()
|
r#type: row.try_get("data_type")?,
|
||||||
.map(|column| column.name().to_string())
|
null: row.try_get("is_nullable")?,
|
||||||
.collect();
|
default: row.try_get("column_default")?,
|
||||||
let mut new_row = vec![];
|
comment: None,
|
||||||
for column in row.columns() {
|
}))
|
||||||
new_row.push(convert_column_value_to_string(&row, column)?)
|
|
||||||
}
|
|
||||||
records.push(new_row)
|
|
||||||
}
|
}
|
||||||
Ok((headers, records))
|
Ok(columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_constraints(
|
||||||
|
&self,
|
||||||
|
_database: &Database,
|
||||||
|
table: &Table,
|
||||||
|
) -> anyhow::Result<Vec<Box<dyn TableRow>>> {
|
||||||
|
let mut rows = sqlx::query(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
tc.constraint_name, tc.table_name, kcu.column_name,
|
||||||
|
ccu.table_name AS foreign_table_name,
|
||||||
|
ccu.column_name AS foreign_column_name
|
||||||
|
FROM
|
||||||
|
information_schema.table_constraints AS tc
|
||||||
|
JOIN information_schema.key_column_usage AS kcu
|
||||||
|
ON tc.constraint_name = kcu.constraint_name
|
||||||
|
JOIN information_schema.constraint_column_usage AS ccu
|
||||||
|
ON ccu.constraint_name = tc.constraint_name
|
||||||
|
WHERE ccu.table_name = $1
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(&table.name)
|
||||||
|
.fetch(&self.pool);
|
||||||
|
let mut constraints: Vec<Box<dyn TableRow>> = vec![];
|
||||||
|
while let Some(row) = rows.try_next().await? {
|
||||||
|
constraints.push(Box::new(Constraint {
|
||||||
|
name: row.try_get("constraint_name")?,
|
||||||
|
column_name: row.try_get("column_name")?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Ok(constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn close(&self) {
|
async fn close(&self) {
|
||||||
|
Loading…
Reference in New Issue
Block a user