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 ExplainQueryTool;
impl SqliteServerTool for ExplainQueryTool {
const NAME: &str = "explain_query";
type Context = McpServerSqlite;
type Error = ToolError<ExplainQueryError>;
type Input = ExplainQueryInput;
type Output = ExplainQueryOutput;
fn handle(
ctx: &Self::Context,
input: Self::Input,
) -> Result<Self::Output, Self::Error> {
let conn = ctx
.connection()
.map_err(|source| ToolError::Connection { source })?;
let explain_sql = format!("EXPLAIN QUERY PLAN {}", input.query);
let mut stmt =
conn.prepare(&explain_sql).map_err(|source| {
if matches!(
source,
rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error {
code: rusqlite::ffi::ErrorCode::AuthorizationForStatementDenied,
..
},
_,
)
) {
ToolError::AccessDenied {
message: format!(
"the configured access control policy \
denied this statement: {}",
input.query,
),
}
} else {
ToolError::Tool(ExplainQueryError::Query { source })
}
})?;
let plan = stmt
.query_map([], |row| {
Ok(PlanNode {
id: row.get(0)?,
parent: row.get(1)?,
detail: row.get(3)?,
})
})
.map_err(|source| {
ToolError::Tool(ExplainQueryError::Query { source })
})?
.collect::<Result<Vec<_>, _>>()
.map_err(|source| {
ToolError::Tool(ExplainQueryError::Query { source })
})?;
Ok(ExplainQueryOutput { plan })
}
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct ExplainQueryInput {
#[schemars(description = "The SQL query to explain")]
pub query: String,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct ExplainQueryOutput {
pub plan: Vec<PlanNode>,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct PlanNode {
pub id: i64,
pub parent: i64,
pub detail: String,
}
#[derive(Debug, thiserror::Error)]
pub enum ExplainQueryError {
#[error("failed to explain query: {source}")]
Query {
source: rusqlite::Error,
},
}
impl IntoContents for ExplainQueryError {
fn into_contents(self) -> Vec<Content> {
vec![Content::text(self.to_string())]
}
}