dbmcp_postgres/tools/
list_views.rs1use dbmcp_server::pagination::{Cursor, Pager};
4
5use super::prelude::*;
6use crate::types::{ListEntriesResponse, ListViewsRequest};
7
8const NAME: &str = "listViews";
9const TITLE: &str = "List Views";
10const DESCRIPTION_PINNED: &str = include_str!("../../assets/tools/list_views/pinned.md");
11const DESCRIPTION_UNPINNED: &str = include_str!("../../assets/tools/list_views/unpinned.md");
12
13fn annotations() -> ToolAnnotations {
14 ToolAnnotations::new()
15 .read_only(true)
16 .destructive(false)
17 .idempotent(true)
18 .open_world(false)
19}
20
21pub(crate) struct PinnedListViewsTool;
23
24impl ToolBase for PinnedListViewsTool {
25 type Parameter = ListViewsRequest;
26 type Output = ListEntriesResponse;
27 type Error = ErrorData;
28
29 fn name() -> Cow<'static, str> {
30 NAME.into()
31 }
32
33 fn title() -> Option<String> {
34 Some(TITLE.into())
35 }
36
37 fn description() -> Option<Cow<'static, str>> {
38 Some(DESCRIPTION_PINNED.into())
39 }
40
41 fn annotations() -> Option<ToolAnnotations> {
42 Some(annotations())
43 }
44
45 fn input_schema() -> Option<Arc<JsonObject>> {
46 Some(input_schema::<Self::Parameter>(true))
47 }
48
49 fn output_schema() -> Option<Arc<JsonObject>> {
50 Some(output_schema::<Self::Output>())
51 }
52}
53
54impl AsyncTool<PostgresHandler> for PinnedListViewsTool {
55 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
56 handler
57 .list_views(None, params.cursor, params.search, params.detailed)
58 .await
59 }
60}
61
62pub(crate) struct UnpinnedListViewsTool;
64
65impl ToolBase for UnpinnedListViewsTool {
66 type Parameter = ListViewsRequest;
67 type Output = ListEntriesResponse;
68 type Error = ErrorData;
69
70 fn name() -> Cow<'static, str> {
71 NAME.into()
72 }
73
74 fn title() -> Option<String> {
75 Some(TITLE.into())
76 }
77
78 fn description() -> Option<Cow<'static, str>> {
79 Some(DESCRIPTION_UNPINNED.into())
80 }
81
82 fn annotations() -> Option<ToolAnnotations> {
83 Some(annotations())
84 }
85
86 fn input_schema() -> Option<Arc<JsonObject>> {
87 Some(input_schema::<Self::Parameter>(false))
88 }
89
90 fn output_schema() -> Option<Arc<JsonObject>> {
91 Some(output_schema::<Self::Output>())
92 }
93}
94
95impl AsyncTool<PostgresHandler> for UnpinnedListViewsTool {
96 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
97 handler
98 .list_views(params.database, params.cursor, params.search, params.detailed)
99 .await
100 }
101}
102
103const BRIEF_SQL: &str = r"
111 SELECT viewname
112 FROM pg_views
113 WHERE schemaname = 'public'
114 AND ($1::text IS NULL OR viewname ILIKE '%' || $1 || '%')
115 ORDER BY viewname
116 LIMIT $2 OFFSET $3";
117
118const DETAILED_SQL: &str = r"
127 SELECT
128 v.viewname AS name,
129 json_build_object(
130 'schema', v.schemaname,
131 'owner', v.viewowner,
132 'description', pg_catalog.obj_description(c.oid, 'pg_class'),
133 'definition', v.definition
134 ) AS entry
135 FROM pg_views v
136 JOIN pg_namespace n ON n.nspname = v.schemaname
137 JOIN pg_class c ON c.relname = v.viewname AND c.relnamespace = n.oid
138 WHERE v.schemaname = 'public'
139 AND ($1::text IS NULL OR v.viewname ILIKE '%' || $1 || '%')
140 ORDER BY v.viewname
141 LIMIT $2 OFFSET $3";
142
143impl PostgresHandler {
144 pub async fn list_views(
152 &self,
153 database: Option<String>,
154 cursor: Option<Cursor>,
155 search: Option<String>,
156 detailed: bool,
157 ) -> Result<ListEntriesResponse, ErrorData> {
158 let database = database.as_deref().map(str::trim).filter(|s| !s.is_empty());
159 let pattern = search.as_deref().map(str::trim).filter(|s| !s.is_empty());
160 let pager = Pager::new(cursor, self.config.page_size);
161
162 if detailed {
163 let rows: Vec<(String, sqlx::types::Json<serde_json::Value>)> = self
164 .connection
165 .fetch(
166 sqlx::query(DETAILED_SQL)
167 .bind(pattern)
168 .bind(pager.limit())
169 .bind(pager.offset()),
170 database,
171 )
172 .await?;
173 let (rows, next_cursor) = pager.paginate(rows);
174 return Ok(ListEntriesResponse::detailed(
175 rows.into_iter().map(|(key, json)| (key, json.0)).collect(),
176 next_cursor,
177 ));
178 }
179
180 let rows: Vec<String> = self
181 .connection
182 .fetch_scalar(
183 sqlx::query(BRIEF_SQL)
184 .bind(pattern)
185 .bind(pager.limit())
186 .bind(pager.offset()),
187 database,
188 )
189 .await?;
190 let (views, next_cursor) = pager.paginate(rows);
191 Ok(ListEntriesResponse::brief(views, next_cursor))
192 }
193}