Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions mcp-server/src/cbmcp/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,15 @@ async def list_user_permissions(self, username: str) -> list[dict]:
"n.nspname as schema_name, "
"c.relname as object_name, "
"c.relkind as object_type, "
"p.perm as permission "
"p.privilege_type as permission "
"FROM pg_class c "
"JOIN pg_namespace n ON n.oid = c.relnamespace "
"CROSS JOIN LATERAL aclexplode(c.relacl) p "
"WHERE p.grantee = (SELECT oid FROM pg_user WHERE usename = $1) "
"CROSS JOIN LATERAL aclexplode(coalesce(c.relacl, "
"acldefault((CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END)::\"char\", c.relowner))) p "
"WHERE p.grantee = (SELECT usesysid FROM pg_user WHERE usename = $1) "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also handle a NULL relacl here? For a newly created table, relacl is normally NULL, which means the object has its default privileges rather than no privileges. aclexplode(c.relacl) therefore produces no rows, so this function can still report no permissions for the table owner.

information_schema.table_privileges handles this with aclexplode(coalesce(relacl, acldefault('r', relowner))). Could we apply equivalent handling here? It would also be helpful to clarify whether this function is intended to return only direct grants or effective permissions including PUBLIC and inherited role privileges.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I've fixed this.

"AND c.relkind IN ('r', 'v', 'm', 'f', 'p', 'S') "
"AND n.nspname NOT LIKE 'pg_%' "
"ORDER BY n.nspname, c.relname",
"ORDER BY n.nspname, c.relname, p.privilege_type",
username
)
return [
Expand Down Expand Up @@ -643,15 +645,16 @@ async def get_table_bloat_info(self) -> list[dict]:
async with self.get_connection() as conn:
records = await conn.fetch(
"SELECT "
"schemaname, "
"relname as tablename, "
"pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) as total_size, "
"round(100 * (relpages - (relpages * fillfactor / 100)) / relpages, 2) as bloat_ratio "
"FROM pg_class c "
"n.nspname AS schemaname, "
"c.relname AS tablename, "
"pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size, "
"round(100.0 * s.n_dead_tup / GREATEST(s.n_live_tup + s.n_dead_tup, 1), 2)::float8 AS bloat_ratio "
"FROM pg_stat_user_tables s "
"JOIN pg_class c ON c.oid = s.relid "
"JOIN pg_namespace n ON n.oid = c.relnamespace "
"JOIN pg_stat_user_tables s ON s.relid = c.oid "
"WHERE c.relkind = 'r' AND n.nspname NOT LIKE 'pg_%' "
"ORDER BY bloat_ratio DESC "
"WHERE c.relkind = 'r' "
"AND n.nspname NOT LIKE 'pg_%' "
"ORDER BY bloat_ratio DESC, n.nspname, c.relname "
"LIMIT 20"
)
return [
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/tests/test_cbmcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ async def test_get_index_usage(self, client):
async def test_get_table_bloat_info(self, client):
"""Test getting table bloat information"""
try:
result = await client.call_tool("get_table_bloat_info", {"schema": "public", "limit": 5})
result = await client.call_tool("get_table_bloat_info", {})
assert result is not None
assert hasattr(result, 'structured_content')
except Exception as e:
Expand Down
5 changes: 1 addition & 4 deletions mcp-server/tests/test_database_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,7 @@ async def test_get_index_usage(self, client):
async def test_get_table_bloat_info(self, client):
"""Test table bloat information"""
try:
result = await client.call_tool("get_table_bloat_info", {
"schema": "public",
"limit": 5
})
result = await client.call_tool("get_table_bloat_info", {})
assert result is not None
assert hasattr(result, 'structured_content')
except Exception as e:
Expand Down
Loading