Skip to content

Commit 5ec6770

Browse files
committed
added 3 tools
execute_select_query execute_update_query execute_delete_query
1 parent 27b577f commit 5ec6770

File tree

6 files changed

+165
-2
lines changed

6 files changed

+165
-2
lines changed

README-zh.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,27 @@ ORDERS 表定义了哪些约束?
308308
ORDERS 表与哪些表有关联?
309309
```
310310
311+
#### `execute_select_query`
312+
执行 SELECT SQL 查询并返回格式化的结果表。仅允许以 SELECT 开头的查询。
313+
示例:
314+
```
315+
运行:SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID = 10;
316+
```
317+
318+
#### `execute_update_query`
319+
执行 UPDATE SQL 查询并返回受影响的行数。仅允许以 UPDATE 开头的查询。
320+
示例:
321+
```
322+
将部门 10 的所有员工工资上调 10%:UPDATE EMPLOYEES SET SALARY = SALARY * 1.1 WHERE DEPARTMENT_ID = 10;
323+
```
324+
325+
#### `execute_delete_query`
326+
执行 DELETE SQL 查询并返回受影响的行数。仅允许以 DELETE 开头的查询。
327+
示例:
328+
```
329+
删除部门 10 的所有员工:DELETE FROM EMPLOYEES WHERE DEPARTMENT_ID = 10;
330+
```
331+
311332
## 架构
312333
313334
本 MCP 服务器采用三层架构,针对大型 Oracle 数据库进行了优化:

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,27 @@ Example:
308308
What tables are related to the ORDERS table?
309309
```
310310
311+
#### `execute_select_query`
312+
Execute a SELECT SQL query and return the results as a formatted table. Only queries starting with SELECT are allowed.
313+
Example:
314+
```
315+
Run: SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID = 10;
316+
```
317+
318+
#### `execute_update_query`
319+
Execute an UPDATE SQL query and return the number of affected rows. Only queries starting with UPDATE are allowed.
320+
Example:
321+
```
322+
Update the salary for all employees in department 10: UPDATE EMPLOYEES SET SALARY = SALARY * 1.1 WHERE DEPARTMENT_ID = 10;
323+
```
324+
325+
#### `execute_delete_query`
326+
Execute a DELETE SQL query and return the number of affected rows. Only queries starting with DELETE are allowed.
327+
Example:
328+
```
329+
Delete all employees in department 10: DELETE FROM EMPLOYEES WHERE DEPARTMENT_ID = 10;
330+
```
331+
311332
## Architecture
312333
313334
This MCP server employs a three-layer architecture optimized for large-scale Oracle databases:

db_context/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,16 @@ async def get_related_tables(self, table_name: str) -> Dict[str, List[str]]:
135135

136136
async def explain_query_plan(self, query: str) -> Dict[str, Any]:
137137
"""Get execution plan for an SQL query with optimization suggestions"""
138-
return await self.db_connector.explain_query_plan(query)
138+
return await self.db_connector.explain_query_plan(query)
139+
140+
async def execute_select_query(self, query: str) -> list:
141+
"""Execute a SELECT query and return the results as a list of dicts."""
142+
return await self.db_connector.execute_select_query(query)
143+
144+
async def execute_update_query(self, query: str) -> int:
145+
"""Execute an UPDATE query and return the number of affected rows."""
146+
return await self.db_connector.execute_update_query(query)
147+
148+
async def execute_delete_query(self, query: str) -> int:
149+
"""Execute a DELETE query and return the number of affected rows."""
150+
return await self.db_connector.execute_delete_query(query)

db_context/database.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,46 @@ def _analyze_query_for_optimization(self, query: str) -> List[str]:
811811

812812
return suggestions
813813

814+
async def execute_select_query(self, query: str) -> list:
815+
"""Execute a SELECT query and return the results as a list of dicts. Only allows queries starting with SELECT."""
816+
conn = await self.get_connection()
817+
try:
818+
cursor = conn.cursor()
819+
await cursor.execute(query) if not self.thick_mode else cursor.execute(query)
820+
columns = [col[0] for col in cursor.description]
821+
if self.thick_mode:
822+
rows = cursor.fetchall()
823+
else:
824+
rows = await cursor.fetchall()
825+
result = [dict(zip(columns, row)) for row in rows]
826+
return result
827+
finally:
828+
await self._close_connection(conn)
829+
830+
async def execute_update_query(self, query: str) -> int:
831+
"""Execute an UPDATE query and return the number of affected rows. Only allows queries starting with UPDATE."""
832+
conn = await self.get_connection()
833+
try:
834+
cursor = conn.cursor()
835+
await cursor.execute(query) if not self.thick_mode else cursor.execute(query)
836+
rowcount = cursor.rowcount
837+
await self._commit(conn)
838+
return rowcount
839+
finally:
840+
await self._close_connection(conn)
841+
842+
async def execute_delete_query(self, query: str) -> int:
843+
"""Execute a DELETE query and return the number of affected rows. Only allows queries starting with DELETE."""
844+
conn = await self.get_connection()
845+
try:
846+
cursor = conn.cursor()
847+
await cursor.execute(query) if not self.thick_mode else cursor.execute(query)
848+
rowcount = cursor.rowcount
849+
await self._commit(conn)
850+
return rowcount
851+
finally:
852+
await self._close_connection(conn)
853+
814854
async def _close_connection(self, conn):
815855
"""Helper method to close connection based on mode"""
816856
try:

db_context/schema/formatter.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,18 @@ def _format_relationship_groups(groups: List[Dict[str, Any]], result: List[str])
359359
else:
360360
result.append(f" - {group['pattern']}:")
361361
for pattern in sorted(group['column_patterns']):
362-
result.append(f" {pattern}")
362+
result.append(f" {pattern}")
363+
364+
def format_select_results(rows: list) -> str:
365+
"""Format SELECT query results (list of dicts) as a readable table string."""
366+
if not rows:
367+
return "No results."
368+
columns = list(rows[0].keys())
369+
# Calculate max width for each column
370+
col_widths = {col: max(len(str(col)), max(len(str(row.get(col, ''))) for row in rows)) for col in columns}
371+
# Header
372+
header = " | ".join(col.ljust(col_widths[col]) for col in columns)
373+
sep = "-+-".join("-" * col_widths[col] for col in columns)
374+
# Rows
375+
row_lines = [" | ".join(str(row.get(col, '')).ljust(col_widths[col]) for col in columns) for row in rows]
376+
return f"{header}\n{sep}\n" + "\n".join(row_lines)

main.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from dotenv import load_dotenv
1010

1111
from db_context import DatabaseContext
12+
from db_context.schema.formatter import format_select_results
1213

1314
# Load environment variables from .env file
1415
load_dotenv()
@@ -607,5 +608,59 @@ async def get_related_tables(table_name: str, ctx: Context) -> str:
607608
except Exception as e:
608609
return f"Error getting related tables: {str(e)}"
609610

611+
@mcp.tool()
612+
async def execute_select_query(query: str, ctx: Context) -> str:
613+
"""
614+
Execute a SELECT SQL query and return the results as a formatted table. Only queries starting with SELECT are allowed.
615+
Args:
616+
query: The SQL SELECT query to execute. Must start with SELECT (case-insensitive).
617+
Returns:
618+
A formatted string table of the query results, or an error message if the query is invalid or fails.
619+
"""
620+
db_context: DatabaseContext = ctx.request_context.lifespan_context
621+
if not query.strip().lower().startswith("select"):
622+
return "Error: Only SELECT queries are allowed."
623+
try:
624+
rows = await db_context.execute_select_query(query)
625+
return format_select_results(rows)
626+
except Exception as e:
627+
return f"Error executing query: {str(e)}"
628+
629+
@mcp.tool()
630+
async def execute_update_query(query: str, ctx: Context) -> str:
631+
"""
632+
Execute an UPDATE SQL query and return the number of affected rows. Only queries starting with UPDATE are allowed.
633+
Args:
634+
query: The SQL UPDATE query to execute. Must start with UPDATE (case-insensitive).
635+
Returns:
636+
A string indicating the number of affected rows, or an error message if the query is invalid or fails.
637+
"""
638+
db_context: DatabaseContext = ctx.request_context.lifespan_context
639+
if not query.strip().lower().startswith("update"):
640+
return "Error: Only UPDATE queries are allowed."
641+
try:
642+
count = await db_context.execute_update_query(query)
643+
return f"Rows affected: {count}"
644+
except Exception as e:
645+
return f"Error executing update: {str(e)}"
646+
647+
@mcp.tool()
648+
async def execute_delete_query(query: str, ctx: Context) -> str:
649+
"""
650+
Execute a DELETE SQL query and return the number of affected rows. Only queries starting with DELETE are allowed.
651+
Args:
652+
query: The SQL DELETE query to execute. Must start with DELETE (case-insensitive).
653+
Returns:
654+
A string indicating the number of affected rows, or an error message if the query is invalid or fails.
655+
"""
656+
db_context: DatabaseContext = ctx.request_context.lifespan_context
657+
if not query.strip().lower().startswith("delete"):
658+
return "Error: Only DELETE queries are allowed."
659+
try:
660+
count = await db_context.execute_delete_query(query)
661+
return f"Rows affected: {count}"
662+
except Exception as e:
663+
return f"Error executing delete: {str(e)}"
664+
610665
if __name__ == "__main__":
611666
mcp.run()

0 commit comments

Comments
 (0)