dbmcp_sqlite/tools/
list_triggers.rs1use std::borrow::Cow;
4
5use dbmcp_server::pagination::Pager;
6use dbmcp_server::types::ListTriggersResponse;
7
8use dbmcp_sql::Connection as _;
9use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
10use rmcp::model::{ErrorData, ToolAnnotations};
11
12use crate::SqliteHandler;
13use crate::types::ListTriggersRequest;
14
15pub(crate) struct ListTriggersTool;
17
18impl ListTriggersTool {
19 const NAME: &'static str = "listTriggers";
20 const TITLE: &'static str = "List Triggers";
21 const DESCRIPTION: &'static str = include_str!("../../assets/tools/list_triggers.md");
22}
23
24impl ToolBase for ListTriggersTool {
25 type Parameter = ListTriggersRequest;
26 type Output = ListTriggersResponse;
27 type Error = ErrorData;
28
29 fn name() -> Cow<'static, str> {
30 Self::NAME.into()
31 }
32
33 fn title() -> Option<String> {
34 Some(Self::TITLE.into())
35 }
36
37 fn description() -> Option<Cow<'static, str>> {
38 Some(Self::DESCRIPTION.into())
39 }
40
41 fn annotations() -> Option<ToolAnnotations> {
42 Some(
43 ToolAnnotations::new()
44 .read_only(true)
45 .destructive(false)
46 .idempotent(true)
47 .open_world(false),
48 )
49 }
50}
51
52impl AsyncTool<SqliteHandler> for ListTriggersTool {
53 async fn invoke(handler: &SqliteHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
54 handler.list_triggers(params).await
55 }
56}
57
58const BRIEF_SQL: &str = r"
67 SELECT name
68 FROM sqlite_schema
69 WHERE type = 'trigger'
70 AND name NOT LIKE 'sqlite_%'
71 AND (?1 IS NULL OR name LIKE '%' || ?1 || '%')
72 ORDER BY name
73 LIMIT ?2 OFFSET ?3";
74
75const DETAILED_SQL: &str = r"
86 SELECT
87 name,
88 json_object(
89 'schema', 'main',
90 'table', tbl_name,
91 'definition', sql
92 ) AS entry
93 FROM sqlite_schema
94 WHERE type = 'trigger'
95 AND name NOT LIKE 'sqlite_%'
96 AND sql IS NOT NULL
97 AND (?1 IS NULL OR name LIKE '%' || ?1 || '%')
98 ORDER BY name
99 LIMIT ?2 OFFSET ?3";
100
101impl SqliteHandler {
102 pub async fn list_triggers(
109 &self,
110 ListTriggersRequest {
111 cursor,
112 search,
113 detailed,
114 }: ListTriggersRequest,
115 ) -> Result<ListTriggersResponse, ErrorData> {
116 let pattern = search.as_deref().map(str::trim).filter(|s| !s.is_empty());
117 let pager = Pager::new(cursor, self.config.page_size);
118
119 if detailed {
120 let rows: Vec<(String, sqlx::types::Json<serde_json::Value>)> = self
121 .connection
122 .fetch(
123 sqlx::query(DETAILED_SQL)
124 .bind(pattern)
125 .bind(pager.limit())
126 .bind(pager.offset()),
127 None,
128 )
129 .await?;
130 let (rows, next_cursor) = pager.paginate(rows);
131 return Ok(ListTriggersResponse::detailed(
132 rows.into_iter().map(|(name, json)| (name, json.0)).collect(),
133 next_cursor,
134 ));
135 }
136
137 let rows: Vec<String> = self
138 .connection
139 .fetch_scalar(
140 sqlx::query(BRIEF_SQL)
141 .bind(pattern)
142 .bind(pager.limit())
143 .bind(pager.offset()),
144 None,
145 )
146 .await?;
147 let (triggers, next_cursor) = pager.paginate(rows);
148 Ok(ListTriggersResponse::brief(triggers, next_cursor))
149 }
150}