use rmcp::model::{Content, IntoContents};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::ToolError;
use crate::{mcp::McpServerSqlite, traits::SqliteServerTool};
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
Serialize,
Deserialize,
JsonSchema,
)]
pub struct DatabaseInfoTool;
impl SqliteServerTool for DatabaseInfoTool {
const NAME: &str = "database_info";
type Context = McpServerSqlite;
type Error = ToolError<DatabaseInfoError>;
type Input = DatabaseInfoInput;
type Output = DatabaseInfoOutput;
fn handle(
ctx: &Self::Context,
_input: Self::Input,
) -> Result<Self::Output, Self::Error> {
let conn = ctx
.connection()
.map_err(|source| ToolError::Connection { source })?;
let query_err =
|source| ToolError::Tool(DatabaseInfoError::Query { source });
let sqlite_version: String = conn
.query_row("SELECT sqlite_version()", [], |row| row.get(0))
.map_err(query_err)?;
let page_size: i64 = conn
.query_row("PRAGMA page_size", [], |row| row.get(0))
.map_err(query_err)?;
let page_count: i64 = conn
.query_row("PRAGMA page_count", [], |row| row.get(0))
.map_err(query_err)?;
let journal_mode: String = conn
.query_row("PRAGMA journal_mode", [], |row| row.get(0))
.map_err(query_err)?;
let wal_checkpoint = if journal_mode == "wal" {
let info = conn
.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |row| {
Ok(WalCheckpointInfo {
busy: row.get(0)?,
log_pages: row.get(1)?,
checkpointed_pages: row.get(2)?,
})
})
.map_err(query_err)?;
Some(info)
} else {
None
};
let database_size_bytes = page_size * page_count;
let freelist_count: i64 = conn
.query_row("PRAGMA freelist_count", [], |row| row.get(0))
.map_err(query_err)?;
let table_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'table'",
[],
|row| row.get(0),
)
.map_err(query_err)?;
let index_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'index'",
[],
|row| row.get(0),
)
.map_err(query_err)?;
Ok(DatabaseInfoOutput {
sqlite_version,
page_size,
page_count,
journal_mode,
wal_checkpoint,
database_size_bytes,
freelist_count,
table_count,
index_count,
})
}
}
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct DatabaseInfoInput {}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct DatabaseInfoOutput {
pub sqlite_version: String,
pub page_size: i64,
pub page_count: i64,
pub journal_mode: String,
pub wal_checkpoint: Option<WalCheckpointInfo>,
pub database_size_bytes: i64,
pub freelist_count: i64,
pub table_count: i64,
pub index_count: i64,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct WalCheckpointInfo {
pub busy: i64,
pub log_pages: i64,
pub checkpointed_pages: i64,
}
#[derive(Debug, thiserror::Error)]
pub enum DatabaseInfoError {
#[error("failed to retrieve database info: {source}")]
Query {
source: rusqlite::Error,
},
}
impl IntoContents for DatabaseInfoError {
fn into_contents(self) -> Vec<Content> {
vec![Content::text(self.to_string())]
}
}