Show more data on show-db
All checks were successful
Build and test / build (push) Successful in 2m42s
Build and test / check (push) Successful in 2m42s
Build and test / check-license (push) Successful in 5m31s
Build and test / test (push) Successful in 3m6s
Build and test / docs (push) Successful in 6m58s

This commit is contained in:
2025-12-15 11:43:59 +09:00
parent 1cf9273fcd
commit 1991e7bfd8
2 changed files with 96 additions and 12 deletions

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use prettytable::{Cell, Row, Table};
use itertools::Itertools;
use prettytable::Table;
use serde::{Deserialize, Serialize};
use serde_json::json;
@@ -40,10 +41,25 @@ pub fn print_list_databases_output_status(output: &ListDatabasesResponse) {
println!("No databases to show.");
} else {
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 {
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();
}
}
@@ -52,11 +68,15 @@ pub fn print_list_databases_output_status_json(output: &ListDatabasesResponse) {
let value = output
.iter()
.map(|(name, result)| match result {
Ok(_row) => (
Ok(row) => (
name.to_string(),
json!({
"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) => (

View File

@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::core::protocol::CompleteDatabaseNameResponse;
use crate::core::types::MySQLDatabase;
use crate::core::types::MySQLUser;
use crate::{
core::{
common::UnixUser,
@@ -207,12 +208,42 @@ pub async fn drop_databases(
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DatabaseRow {
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 {
fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
Ok(DatabaseRow {
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>(
r#"
SELECT CAST(`SCHEMA_NAME` AS CHAR(64)) AS `database`
FROM `information_schema`.`SCHEMATA`
WHERE `SCHEMA_NAME` = ?
"#,
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`
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())
.fetch_optional(&mut *connection)
@@ -263,6 +309,8 @@ pub async fn list_databases(
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);
}
@@ -276,10 +324,24 @@ pub async fn list_all_databases_for_user(
) -> ListAllDatabasesResponse {
let result = sqlx::query_as::<_, DatabaseRow>(
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`
WHERE `SCHEMA_NAME` NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
AND `SCHEMA_NAME` REGEXP ?
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` 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))
@@ -287,6 +349,8 @@ pub async fn list_all_databases_for_user(
.await
.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 {
tracing::error!(
"Failed to list databases for user '{}': {:?}",