Show more data on show-db
All checks were successful
All checks were successful
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use prettytable::{Cell, Row, Table};
|
use itertools::Itertools;
|
||||||
|
use prettytable::Table;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@@ -40,10 +41,25 @@ pub fn print_list_databases_output_status(output: &ListDatabasesResponse) {
|
|||||||
println!("No databases to show.");
|
println!("No databases to show.");
|
||||||
} else {
|
} else {
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(Row::new(vec![Cell::new("Database")]));
|
table.add_row(row![
|
||||||
|
"Database",
|
||||||
|
"Tables",
|
||||||
|
"Users",
|
||||||
|
"Collation",
|
||||||
|
"Character Set",
|
||||||
|
"Size (Bytes)"
|
||||||
|
]);
|
||||||
for db in final_database_list {
|
for db in final_database_list {
|
||||||
table.add_row(row![db.database]);
|
table.add_row(row![
|
||||||
|
db.database,
|
||||||
|
db.tables.join("\n"),
|
||||||
|
db.users.iter().map(|user| user.as_str()).join("\n"),
|
||||||
|
db.collation.as_deref().unwrap_or("N/A"),
|
||||||
|
db.character_set.as_deref().unwrap_or("N/A"),
|
||||||
|
db.size_bytes,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.printstd();
|
table.printstd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,11 +68,15 @@ pub fn print_list_databases_output_status_json(output: &ListDatabasesResponse) {
|
|||||||
let value = output
|
let value = output
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, result)| match result {
|
.map(|(name, result)| match result {
|
||||||
Ok(_row) => (
|
Ok(row) => (
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
json!({
|
json!({
|
||||||
"status": "success",
|
"status": "success",
|
||||||
// NOTE: there will likely be more data to include here in the future
|
"tables": row.tables,
|
||||||
|
"users": row.users,
|
||||||
|
"collation": row.collation,
|
||||||
|
"character_set": row.character_set,
|
||||||
|
"size_bytes": row.size_bytes,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Err(err) => (
|
Err(err) => (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::core::protocol::CompleteDatabaseNameResponse;
|
use crate::core::protocol::CompleteDatabaseNameResponse;
|
||||||
use crate::core::types::MySQLDatabase;
|
use crate::core::types::MySQLDatabase;
|
||||||
|
use crate::core::types::MySQLUser;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{
|
||||||
common::UnixUser,
|
common::UnixUser,
|
||||||
@@ -207,12 +208,42 @@ pub async fn drop_databases(
|
|||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct DatabaseRow {
|
pub struct DatabaseRow {
|
||||||
pub database: MySQLDatabase,
|
pub database: MySQLDatabase,
|
||||||
|
pub tables: Vec<String>,
|
||||||
|
pub users: Vec<MySQLUser>,
|
||||||
|
pub collation: Option<String>,
|
||||||
|
pub character_set: Option<String>,
|
||||||
|
pub size_bytes: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseRow {
|
impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseRow {
|
||||||
fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
|
fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
|
||||||
Ok(DatabaseRow {
|
Ok(DatabaseRow {
|
||||||
database: row.try_get::<String, _>("database")?.into(),
|
database: row.try_get::<String, _>("database")?.into(),
|
||||||
|
tables: {
|
||||||
|
let s: Option<String> = row.try_get("tables")?;
|
||||||
|
s.and_then(|s| {
|
||||||
|
if s.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(s.split(',').map(|s| s.to_owned()).collect())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
let s: Option<String> = row.try_get("users")?;
|
||||||
|
s.and_then(|s| {
|
||||||
|
if s.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(s.split(',').map(|s| s.to_owned().into()).collect())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
},
|
||||||
|
collation: row.try_get::<Option<String>, _>("collation")?,
|
||||||
|
character_set: row.try_get::<Option<String>, _>("character_set")?,
|
||||||
|
size_bytes: row.try_get::<u64, _>("size_bytes")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,10 +275,25 @@ pub async fn list_databases(
|
|||||||
|
|
||||||
let result = sqlx::query_as::<_, DatabaseRow>(
|
let result = sqlx::query_as::<_, DatabaseRow>(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST(`SCHEMA_NAME` AS CHAR(64)) AS `database`
|
SELECT
|
||||||
|
CAST(`information_schema`.`SCHEMATA`.`SCHEMA_NAME` AS CHAR(64)) AS `database`,
|
||||||
|
GROUP_CONCAT(DISTINCT CAST(`information_schema`.`TABLES`.`TABLE_NAME` AS CHAR(64)) SEPARATOR ',') AS `tables`,
|
||||||
|
GROUP_CONCAT(DISTINCT CAST(`mysql`.`db`.`User` AS CHAR(64)) SEPARATOR ',') AS `users`,
|
||||||
|
MAX(`information_schema`.`SCHEMATA`.`DEFAULT_COLLATION_NAME`) AS `collation`,
|
||||||
|
MAX(`information_schema`.`SCHEMATA`.`DEFAULT_CHARACTER_SET_NAME`) AS `character_set`,
|
||||||
|
CAST(IFNULL(
|
||||||
|
SUM(`information_schema`.`TABLES`.`DATA_LENGTH` + `information_schema`.`TABLES`.`INDEX_LENGTH`),
|
||||||
|
0
|
||||||
|
) AS UNSIGNED INTEGER) AS `size_bytes`
|
||||||
FROM `information_schema`.`SCHEMATA`
|
FROM `information_schema`.`SCHEMATA`
|
||||||
WHERE `SCHEMA_NAME` = ?
|
LEFT OUTER JOIN `information_schema`.`TABLES`
|
||||||
|
ON `information_schema`.`SCHEMATA`.`SCHEMA_NAME` = `TABLES`.`TABLE_SCHEMA`
|
||||||
|
LEFT OUTER JOIN `mysql`.`db`
|
||||||
|
ON `information_schema`.`SCHEMATA`.`SCHEMA_NAME` = `mysql`.`db`.`DB`
|
||||||
|
WHERE `information_schema`.`SCHEMATA`.`SCHEMA_NAME` = ?
|
||||||
|
GROUP BY `information_schema`.`SCHEMATA`.`SCHEMA_NAME`
|
||||||
"#,
|
"#,
|
||||||
|
|
||||||
)
|
)
|
||||||
.bind(database_name.to_string())
|
.bind(database_name.to_string())
|
||||||
.fetch_optional(&mut *connection)
|
.fetch_optional(&mut *connection)
|
||||||
@@ -263,6 +309,8 @@ pub async fn list_databases(
|
|||||||
tracing::error!("Failed to list database '{}': {:?}", &database_name, err);
|
tracing::error!("Failed to list database '{}': {:?}", &database_name, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: should we assert that the users are also owned by the unix_user from the request?
|
||||||
|
|
||||||
results.insert(database_name, result);
|
results.insert(database_name, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,10 +324,24 @@ pub async fn list_all_databases_for_user(
|
|||||||
) -> ListAllDatabasesResponse {
|
) -> ListAllDatabasesResponse {
|
||||||
let result = sqlx::query_as::<_, DatabaseRow>(
|
let result = sqlx::query_as::<_, DatabaseRow>(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST(`SCHEMA_NAME` AS CHAR(64)) AS `database`
|
SELECT
|
||||||
|
CAST(`information_schema`.`SCHEMATA`.`SCHEMA_NAME` AS CHAR(64)) AS `database`,
|
||||||
|
GROUP_CONCAT(DISTINCT CAST(`information_schema`.`TABLES`.`TABLE_NAME` AS CHAR(64)) SEPARATOR ',') AS `tables`,
|
||||||
|
GROUP_CONCAT(DISTINCT CAST(`mysql`.`db`.`User` AS CHAR(64)) SEPARATOR ',') AS `users`,
|
||||||
|
MAX(`information_schema`.`SCHEMATA`.`DEFAULT_COLLATION_NAME`) AS `collation`,
|
||||||
|
MAX(`information_schema`.`SCHEMATA`.`DEFAULT_CHARACTER_SET_NAME`) AS `character_set`,
|
||||||
|
CAST(IFNULL(
|
||||||
|
SUM(`information_schema`.`TABLES`.`DATA_LENGTH` + `information_schema`.`TABLES`.`INDEX_LENGTH`),
|
||||||
|
0
|
||||||
|
) AS UNSIGNED INTEGER) AS `size_bytes`
|
||||||
FROM `information_schema`.`SCHEMATA`
|
FROM `information_schema`.`SCHEMATA`
|
||||||
WHERE `SCHEMA_NAME` NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
|
LEFT OUTER JOIN `information_schema`.`TABLES`
|
||||||
AND `SCHEMA_NAME` REGEXP ?
|
ON `information_schema`.`SCHEMATA`.`SCHEMA_NAME` = `TABLES`.`TABLE_SCHEMA`
|
||||||
|
LEFT OUTER JOIN `mysql`.`db`
|
||||||
|
ON `information_schema`.`SCHEMATA`.`SCHEMA_NAME` = `mysql`.`db`.`DB`
|
||||||
|
WHERE `information_schema`.`SCHEMATA`.`SCHEMA_NAME` NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
|
||||||
|
AND `information_schema`.`SCHEMATA`.`SCHEMA_NAME` REGEXP ?
|
||||||
|
GROUP BY `information_schema`.`SCHEMATA`.`SCHEMA_NAME`
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(create_user_group_matching_regex(unix_user))
|
.bind(create_user_group_matching_regex(unix_user))
|
||||||
@@ -287,6 +349,8 @@ pub async fn list_all_databases_for_user(
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| ListAllDatabasesError::MySqlError(err.to_string()));
|
.map_err(|err| ListAllDatabasesError::MySqlError(err.to_string()));
|
||||||
|
|
||||||
|
// TODO: should we assert that the users are also owned by the unix_user from the request?
|
||||||
|
|
||||||
if let Err(err) = &result {
|
if let Err(err) = &result {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to list databases for user '{}': {:?}",
|
"Failed to list databases for user '{}': {:?}",
|
||||||
|
|||||||
Reference in New Issue
Block a user