use std::borrow::Cow;
use dbmcp_server::pagination::Pager;
use dbmcp_server::types::ListTriggersResponse;
use dbmcp_sql::Connection as _;
use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
use rmcp::model::{ErrorData, ToolAnnotations};
use crate::SqliteHandler;
use crate::types::ListTriggersRequest;
const NAME: &str = "listTriggers";
const TITLE: &str = "List Triggers";
const DESCRIPTION: &str = include_str!("../../assets/tools/list_triggers.md");
pub(crate) struct ListTriggersTool;
impl ToolBase for ListTriggersTool {
type Parameter = ListTriggersRequest;
type Output = ListTriggersResponse;
type Error = ErrorData;
fn name() -> Cow<'static, str> {
NAME.into()
}
fn title() -> Option<String> {
Some(TITLE.into())
}
fn description() -> Option<Cow<'static, str>> {
Some(DESCRIPTION.into())
}
fn annotations() -> Option<ToolAnnotations> {
Some(
ToolAnnotations::new()
.read_only(true)
.destructive(false)
.idempotent(true)
.open_world(false),
)
}
}
impl AsyncTool<SqliteHandler> for ListTriggersTool {
async fn invoke(handler: &SqliteHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
handler.list_triggers(params).await
}
}
const BRIEF_SQL: &str = r"
SELECT name
FROM sqlite_schema
WHERE type = 'trigger'
AND name NOT LIKE 'sqlite_%'
AND (?1 IS NULL OR name LIKE '%' || ?1 || '%')
ORDER BY name
LIMIT ?2 OFFSET ?3";
const DETAILED_SQL: &str = r"
SELECT
name,
json_object(
'schema', 'main',
'table', tbl_name,
'definition', sql
) AS entry
FROM sqlite_schema
WHERE type = 'trigger'
AND name NOT LIKE 'sqlite_%'
AND sql IS NOT NULL
AND (?1 IS NULL OR name LIKE '%' || ?1 || '%')
ORDER BY name
LIMIT ?2 OFFSET ?3";
impl SqliteHandler {
pub async fn list_triggers(
&self,
ListTriggersRequest {
cursor,
search,
detailed,
}: ListTriggersRequest,
) -> Result<ListTriggersResponse, ErrorData> {
let pattern = search.as_deref().map(str::trim).filter(|s| !s.is_empty());
let pager = Pager::new(cursor, self.config.page_size);
if detailed {
let rows: Vec<(String, sqlx::types::Json<serde_json::Value>)> = self
.connection
.fetch(
sqlx::query(DETAILED_SQL)
.bind(pattern)
.bind(pager.limit())
.bind(pager.offset()),
None,
)
.await?;
let (rows, next_cursor) = pager.paginate(rows);
return Ok(ListTriggersResponse::detailed(
rows.into_iter().map(|(name, json)| (name, json.0)).collect(),
next_cursor,
));
}
let rows: Vec<String> = self
.connection
.fetch_scalar(
sqlx::query(BRIEF_SQL)
.bind(pattern)
.bind(pager.limit())
.bind(pager.offset()),
None,
)
.await?;
let (triggers, next_cursor) = pager.paginate(rows);
Ok(ListTriggersResponse::brief(triggers, next_cursor))
}
}