dbmcp_postgres/tools/
list_functions.rs1use std::borrow::Cow;
4
5use dbmcp_server::pagination::Pager;
6use dbmcp_sql::Connection as _;
7use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
8use rmcp::model::{ErrorData, ToolAnnotations};
9
10use crate::PostgresHandler;
11use crate::types::{ListFunctionsRequest, ListFunctionsResponse};
12
13pub(crate) struct ListFunctionsTool;
15
16impl ListFunctionsTool {
17 const NAME: &'static str = "listFunctions";
18 const TITLE: &'static str = "List Functions";
19 const DESCRIPTION: &'static str = include_str!("../../assets/tools/list_functions.md");
20}
21
22impl ToolBase for ListFunctionsTool {
23 type Parameter = ListFunctionsRequest;
24 type Output = ListFunctionsResponse;
25 type Error = ErrorData;
26
27 fn name() -> Cow<'static, str> {
28 Self::NAME.into()
29 }
30
31 fn title() -> Option<String> {
32 Some(Self::TITLE.into())
33 }
34
35 fn description() -> Option<Cow<'static, str>> {
36 Some(Self::DESCRIPTION.into())
37 }
38
39 fn annotations() -> Option<ToolAnnotations> {
40 Some(
41 ToolAnnotations::new()
42 .read_only(true)
43 .destructive(false)
44 .idempotent(true)
45 .open_world(false),
46 )
47 }
48}
49
50impl AsyncTool<PostgresHandler> for ListFunctionsTool {
51 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
52 handler.list_functions(params).await
53 }
54}
55
56const BRIEF_SQL: &str = r"
64 SELECT p.proname
65 FROM pg_proc p
66 JOIN pg_namespace n ON n.oid = p.pronamespace
67 WHERE n.nspname = 'public'
68 AND p.prokind = 'f'
69 AND ($1::text IS NULL OR p.proname ILIKE '%' || $1 || '%')
70 ORDER BY p.proname, p.oid
71 LIMIT $2 OFFSET $3";
72
73const DETAILED_SQL: &str = r"
92 SELECT
93 p.proname || '(' || args.text || ')' AS name,
94 json_build_object(
95 'schema', 'public',
96 'name', p.proname,
97 'language', l.lanname,
98 'arguments', args.text,
99 'returnType', pg_get_function_result(p.oid),
100 'volatility', CASE p.provolatile
101 WHEN 'i' THEN 'IMMUTABLE'
102 WHEN 's' THEN 'STABLE'
103 WHEN 'v' THEN 'VOLATILE'
104 END,
105 'strict', p.proisstrict,
106 'security', CASE WHEN p.prosecdef THEN 'DEFINER' ELSE 'INVOKER' END,
107 'parallelSafety', CASE p.proparallel
108 WHEN 's' THEN 'SAFE'
109 WHEN 'r' THEN 'RESTRICTED'
110 WHEN 'u' THEN 'UNSAFE'
111 END,
112 'owner', r.rolname,
113 'description', pg_catalog.obj_description(p.oid, 'pg_proc'),
114 'definition', pg_get_functiondef(p.oid)
115 ) AS entry
116 FROM pg_proc p
117 JOIN pg_namespace n ON n.oid = p.pronamespace
118 JOIN pg_language l ON l.oid = p.prolang
119 JOIN pg_roles r ON r.oid = p.proowner
120 CROSS JOIN LATERAL (SELECT pg_get_function_arguments(p.oid) AS text) args
121 WHERE n.nspname = 'public'
122 AND p.prokind = 'f'
123 AND ($1::text IS NULL OR p.proname ILIKE '%' || $1 || '%')
124 ORDER BY p.proname, p.oid
125 LIMIT $2 OFFSET $3";
126
127impl PostgresHandler {
128 pub async fn list_functions(
136 &self,
137 ListFunctionsRequest {
138 database,
139 cursor,
140 search,
141 detailed,
142 }: ListFunctionsRequest,
143 ) -> Result<ListFunctionsResponse, ErrorData> {
144 let database = database.as_deref().map(str::trim).filter(|s| !s.is_empty());
145 let pattern = search.as_deref().map(str::trim).filter(|s| !s.is_empty());
146 let pager = Pager::new(cursor, self.config.page_size);
147
148 if detailed {
149 let rows: Vec<(String, sqlx::types::Json<serde_json::Value>)> = self
150 .connection
151 .fetch(
152 sqlx::query(DETAILED_SQL)
153 .bind(pattern)
154 .bind(pager.limit())
155 .bind(pager.offset()),
156 database,
157 )
158 .await?;
159 let (rows, next_cursor) = pager.paginate(rows);
160 return Ok(ListFunctionsResponse::detailed(
161 rows.into_iter().map(|(key, json)| (key, json.0)).collect(),
162 next_cursor,
163 ));
164 }
165
166 let rows: Vec<String> = self
167 .connection
168 .fetch_scalar(
169 sqlx::query(BRIEF_SQL)
170 .bind(pattern)
171 .bind(pager.limit())
172 .bind(pager.offset()),
173 database,
174 )
175 .await?;
176 let (functions, next_cursor) = pager.paginate(rows);
177 Ok(ListFunctionsResponse::brief(functions, next_cursor))
178 }
179}