dbmcp_sqlite/tools/
list_triggers.rs1use dbmcp_server::pagination::Pager;
4use dbmcp_server::types::ListEntriesResponse;
5
6use super::prelude::*;
7use crate::types::ListTriggersRequest;
8
9const NAME: &str = "listTriggers";
10const TITLE: &str = "List Triggers";
11const DESCRIPTION: &str = include_str!("../../assets/tools/list_triggers.md");
12
13pub(crate) struct ListTriggersTool;
15
16impl ToolBase for ListTriggersTool {
17 type Parameter = ListTriggersRequest;
18 type Output = ListEntriesResponse;
19 type Error = ErrorData;
20
21 fn name() -> Cow<'static, str> {
22 NAME.into()
23 }
24
25 fn title() -> Option<String> {
26 Some(TITLE.into())
27 }
28
29 fn description() -> Option<Cow<'static, str>> {
30 Some(DESCRIPTION.into())
31 }
32
33 fn annotations() -> Option<ToolAnnotations> {
34 Some(
35 ToolAnnotations::new()
36 .read_only(true)
37 .destructive(false)
38 .idempotent(true)
39 .open_world(false),
40 )
41 }
42
43 fn input_schema() -> Option<Arc<JsonObject>> {
44 Some(input_schema::<Self::Parameter>(true))
45 }
46
47 fn output_schema() -> Option<Arc<JsonObject>> {
48 Some(output_schema::<Self::Output>())
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<ListEntriesResponse, 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(ListEntriesResponse::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(ListEntriesResponse::brief(triggers, next_cursor))
149 }
150}