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