dbmcp_postgres/tools/
list_materialized_views.rs1use dbmcp_server::pagination::{Cursor, Pager};
4
5use super::prelude::*;
6use crate::types::{ListEntriesResponse, ListMaterializedViewsRequest};
7
8const NAME: &str = "listMaterializedViews";
9const TITLE: &str = "List Materialized Views";
10const DESCRIPTION_PINNED: &str = include_str!("../../assets/tools/list_materialized_views/pinned.md");
11const DESCRIPTION_UNPINNED: &str = include_str!("../../assets/tools/list_materialized_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 PinnedListMaterializedViewsTool;
23
24impl ToolBase for PinnedListMaterializedViewsTool {
25 type Parameter = ListMaterializedViewsRequest;
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 PinnedListMaterializedViewsTool {
55 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
56 handler
57 .list_materialized_views(None, params.cursor, params.search, params.detailed)
58 .await
59 }
60}
61
62pub(crate) struct UnpinnedListMaterializedViewsTool;
64
65impl ToolBase for UnpinnedListMaterializedViewsTool {
66 type Parameter = ListMaterializedViewsRequest;
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 UnpinnedListMaterializedViewsTool {
96 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
97 handler
98 .list_materialized_views(params.database, params.cursor, params.search, params.detailed)
99 .await
100 }
101}
102
103const BRIEF_SQL: &str = r"
112 SELECT matviewname
113 FROM pg_matviews
114 WHERE schemaname = 'public'
115 AND ($1::text IS NULL OR matviewname ILIKE '%' || $1 || '%')
116 ORDER BY matviewname
117 LIMIT $2 OFFSET $3";
118
119const DETAILED_SQL: &str = r"
130 SELECT
131 mv.matviewname AS name,
132 json_build_object(
133 'schema', mv.schemaname,
134 'owner', mv.matviewowner,
135 'description', pg_catalog.obj_description(c.oid, 'pg_class'),
136 'definition', mv.definition,
137 'populated', mv.ispopulated,
138 'indexed', mv.hasindexes
139 ) AS entry
140 FROM pg_matviews mv
141 JOIN pg_namespace n ON n.nspname = mv.schemaname
142 JOIN pg_class c ON c.relname = mv.matviewname AND c.relnamespace = n.oid
143 WHERE mv.schemaname = 'public'
144 AND ($1::text IS NULL OR mv.matviewname ILIKE '%' || $1 || '%')
145 ORDER BY mv.matviewname
146 LIMIT $2 OFFSET $3";
147
148impl PostgresHandler {
149 pub async fn list_materialized_views(
157 &self,
158 database: Option<String>,
159 cursor: Option<Cursor>,
160 search: Option<String>,
161 detailed: bool,
162 ) -> Result<ListEntriesResponse, ErrorData> {
163 let database = database.as_deref().map(str::trim).filter(|s| !s.is_empty());
164 let pattern = search.as_deref().map(str::trim).filter(|s| !s.is_empty());
165 let pager = Pager::new(cursor, self.config.page_size);
166
167 if detailed {
168 let rows: Vec<(String, sqlx::types::Json<serde_json::Value>)> = self
169 .connection
170 .fetch(
171 sqlx::query(DETAILED_SQL)
172 .bind(pattern)
173 .bind(pager.limit())
174 .bind(pager.offset()),
175 database,
176 )
177 .await?;
178 let (rows, next_cursor) = pager.paginate(rows);
179 return Ok(ListEntriesResponse::detailed(
180 rows.into_iter().map(|(key, json)| (key, json.0)).collect(),
181 next_cursor,
182 ));
183 }
184
185 let rows: Vec<String> = self
186 .connection
187 .fetch_scalar(
188 sqlx::query(BRIEF_SQL)
189 .bind(pattern)
190 .bind(pager.limit())
191 .bind(pager.offset()),
192 database,
193 )
194 .await?;
195 let (materialized_views, next_cursor) = pager.paginate(rows);
196 Ok(ListEntriesResponse::brief(materialized_views, next_cursor))
197 }
198}