mcp-dbutils 0.16.0__py3-none-any.whl → 0.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcp_dbutils/base.py CHANGED
@@ -299,6 +299,64 @@ class ConnectionServer:
299
299
  self.send_log(LOG_LEVEL_ERROR, f"Error in list_prompts: {str(e)}")
300
300
  raise
301
301
 
302
+ def _get_config_or_raise(self, connection: str) -> dict:
303
+ """读取配置文件并验证连接配置
304
+
305
+ Args:
306
+ connection: 连接名称
307
+
308
+ Returns:
309
+ dict: 连接配置
310
+
311
+ Raises:
312
+ ConfigurationError: 如果配置文件格式不正确或连接不存在
313
+ """
314
+ with open(self.config_path, 'r') as f:
315
+ config = yaml.safe_load(f)
316
+ if not config or 'connections' not in config:
317
+ raise ConfigurationError("Configuration file must contain 'connections' section")
318
+ if connection not in config['connections']:
319
+ available_connections = list(config['connections'].keys())
320
+ raise ConfigurationError(f"Connection not found: {connection}. Available connections: {available_connections}")
321
+
322
+ db_config = config['connections'][connection]
323
+
324
+ if 'type' not in db_config:
325
+ raise ConfigurationError("Database configuration must include 'type' field")
326
+
327
+ return db_config
328
+
329
+ def _create_handler_for_type(self, db_type: str, connection: str) -> ConnectionHandler:
330
+ """基于数据库类型创建相应的处理器
331
+
332
+ Args:
333
+ db_type: 数据库类型
334
+ connection: 连接名称
335
+
336
+ Returns:
337
+ ConnectionHandler: 数据库连接处理器
338
+
339
+ Raises:
340
+ ConfigurationError: 如果数据库类型不支持或导入失败
341
+ """
342
+ self.send_log(LOG_LEVEL_DEBUG, f"Creating handler for database type: {db_type}")
343
+
344
+ try:
345
+ if db_type == 'sqlite':
346
+ from .sqlite.handler import SQLiteHandler
347
+ return SQLiteHandler(self.config_path, connection, self.debug)
348
+ elif db_type == 'postgres':
349
+ from .postgres.handler import PostgreSQLHandler
350
+ return PostgreSQLHandler(self.config_path, connection, self.debug)
351
+ elif db_type == 'mysql':
352
+ from .mysql.handler import MySQLHandler
353
+ return MySQLHandler(self.config_path, connection, self.debug)
354
+ else:
355
+ raise ConfigurationError(f"Unsupported database type: {db_type}")
356
+ except ImportError as e:
357
+ # 捕获导入错误并转换为ConfigurationError,以保持与现有测试兼容
358
+ raise ConfigurationError(f"Failed to import handler for {db_type}: {str(e)}")
359
+
302
360
  @asynccontextmanager
303
361
  async def get_handler(self, connection: str) -> AsyncContextManager[ConnectionHandler]:
304
362
  """Get connection handler
@@ -311,74 +369,385 @@ class ConnectionServer:
311
369
  Returns:
312
370
  AsyncContextManager[ConnectionHandler]: Context manager for connection handler
313
371
  """
314
- # Read configuration file to determine database type
315
- with open(self.config_path, 'r') as f:
316
- config = yaml.safe_load(f)
317
- if not config or 'connections' not in config:
318
- raise ConfigurationError("Configuration file must contain 'connections' section")
319
- if connection not in config['connections']:
320
- available_connections = list(config['connections'].keys())
321
- raise ConfigurationError(f"Connection not found: {connection}. Available connections: {available_connections}")
372
+ # Read configuration file and validate connection
373
+ db_config = self._get_config_or_raise(connection)
374
+
375
+ # Create appropriate handler based on database type
376
+ handler = None
377
+ try:
378
+ db_type = db_config['type']
379
+ handler = self._create_handler_for_type(db_type, connection)
322
380
 
323
- db_config = config['connections'][connection]
381
+ # Set session for MCP logging
382
+ if hasattr(self.server, 'session'):
383
+ handler._session = self.server.session
324
384
 
325
- handler = None
326
- try:
327
- if 'type' not in db_config:
328
- raise ConfigurationError("Database configuration must include 'type' field")
329
-
330
- db_type = db_config['type']
331
- self.send_log(LOG_LEVEL_DEBUG, f"Creating handler for database type: {db_type}")
332
- if db_type == 'sqlite':
333
- from .sqlite.handler import SQLiteHandler
334
- handler = SQLiteHandler(self.config_path, connection, self.debug)
335
- elif db_type == 'postgres':
336
- from .postgres.handler import PostgreSQLHandler
337
- handler = PostgreSQLHandler(self.config_path, connection, self.debug)
338
- elif db_type == 'mysql':
339
- from .mysql.handler import MySQLHandler
340
- handler = MySQLHandler(self.config_path, connection, self.debug)
341
- else:
342
- raise ConfigurationError(f"Unsupported database type: {db_type}")
343
-
344
- # Set session for MCP logging
345
- if hasattr(self.server, 'session'):
346
- handler._session = self.server.session
347
-
348
- handler.stats.record_connection_start()
349
- self.send_log(LOG_LEVEL_DEBUG, f"Handler created successfully for {connection}")
350
- # 使用通用的方式处理统计信息序列化
385
+ handler.stats.record_connection_start()
386
+ self.send_log(LOG_LEVEL_DEBUG, f"Handler created successfully for {connection}")
387
+
388
+ yield handler
389
+ finally:
390
+ if handler:
391
+ self.send_log(LOG_LEVEL_DEBUG, f"Cleaning up handler for {connection}")
392
+ handler.stats.record_connection_end()
393
+
394
+ if hasattr(handler, 'cleanup') and callable(handler.cleanup):
395
+ await handler.cleanup()
396
+
397
+ def _get_available_tools(self) -> list[types.Tool]:
398
+ """返回所有可用的数据库工具列表
399
+
400
+ Returns:
401
+ list[types.Tool]: 工具列表
402
+ """
403
+ return [
404
+ types.Tool(
405
+ name="dbutils-run-query",
406
+ description="Execute read-only SQL query on database connection",
407
+ inputSchema={
408
+ "type": "object",
409
+ "properties": {
410
+ "connection": {
411
+ "type": "string",
412
+ "description": DATABASE_CONNECTION_NAME
413
+ },
414
+ "sql": {
415
+ "type": "string",
416
+ "description": "SQL query (SELECT only)"
417
+ }
418
+ },
419
+ "required": ["connection", "sql"]
420
+ }
421
+ ),
422
+ types.Tool(
423
+ name="dbutils-list-tables",
424
+ description="List all available tables in the specified database connection",
425
+ inputSchema={
426
+ "type": "object",
427
+ "properties": {
428
+ "connection": {
429
+ "type": "string",
430
+ "description": DATABASE_CONNECTION_NAME
431
+ }
432
+ },
433
+ "required": ["connection"]
434
+ }
435
+ ),
436
+ types.Tool(
437
+ name="dbutils-describe-table",
438
+ description="Get detailed information about a table's structure",
439
+ inputSchema={
440
+ "type": "object",
441
+ "properties": {
442
+ "connection": {
443
+ "type": "string",
444
+ "description": DATABASE_CONNECTION_NAME
445
+ },
446
+ "table": {
447
+ "type": "string",
448
+ "description": "Table name to describe"
449
+ }
450
+ },
451
+ "required": ["connection", "table"]
452
+ }
453
+ ),
454
+ types.Tool(
455
+ name="dbutils-get-ddl",
456
+ description="Get DDL statement for creating the table",
457
+ inputSchema={
458
+ "type": "object",
459
+ "properties": {
460
+ "connection": {
461
+ "type": "string",
462
+ "description": DATABASE_CONNECTION_NAME
463
+ },
464
+ "table": {
465
+ "type": "string",
466
+ "description": "Table name to get DDL for"
467
+ }
468
+ },
469
+ "required": ["connection", "table"]
470
+ }
471
+ ),
472
+ types.Tool(
473
+ name="dbutils-list-indexes",
474
+ description="List all indexes on the specified table",
475
+ inputSchema={
476
+ "type": "object",
477
+ "properties": {
478
+ "connection": {
479
+ "type": "string",
480
+ "description": DATABASE_CONNECTION_NAME
481
+ },
482
+ "table": {
483
+ "type": "string",
484
+ "description": "Table name to list indexes for"
485
+ }
486
+ },
487
+ "required": ["connection", "table"]
488
+ }
489
+ ),
490
+ types.Tool(
491
+ name="dbutils-get-stats",
492
+ description="Get table statistics like row count and size",
493
+ inputSchema={
494
+ "type": "object",
495
+ "properties": {
496
+ "connection": {
497
+ "type": "string",
498
+ "description": DATABASE_CONNECTION_NAME
499
+ },
500
+ "table": {
501
+ "type": "string",
502
+ "description": "Table name to get statistics for"
503
+ }
504
+ },
505
+ "required": ["connection", "table"]
506
+ }
507
+ ),
508
+ types.Tool(
509
+ name="dbutils-list-constraints",
510
+ description="List all constraints (primary key, foreign keys, etc) on the table",
511
+ inputSchema={
512
+ "type": "object",
513
+ "properties": {
514
+ "connection": {
515
+ "type": "string",
516
+ "description": DATABASE_CONNECTION_NAME
517
+ },
518
+ "table": {
519
+ "type": "string",
520
+ "description": "Table name to list constraints for"
521
+ }
522
+ },
523
+ "required": ["connection", "table"]
524
+ }
525
+ ),
526
+ types.Tool(
527
+ name="dbutils-explain-query",
528
+ description="Get execution plan for a SQL query",
529
+ inputSchema={
530
+ "type": "object",
531
+ "properties": {
532
+ "connection": {
533
+ "type": "string",
534
+ "description": DATABASE_CONNECTION_NAME
535
+ },
536
+ "sql": {
537
+ "type": "string",
538
+ "description": "SQL query to explain"
539
+ }
540
+ },
541
+ "required": ["connection", "sql"]
542
+ }
543
+ ),
544
+ types.Tool(
545
+ name="dbutils-get-performance",
546
+ description="Get database performance statistics",
547
+ inputSchema={
548
+ "type": "object",
549
+ "properties": {
550
+ "connection": {
551
+ "type": "string",
552
+ "description": DATABASE_CONNECTION_NAME
553
+ }
554
+ },
555
+ "required": ["connection"]
556
+ }
557
+ ),
558
+ types.Tool(
559
+ name="dbutils-analyze-query",
560
+ description="Analyze a SQL query for performance",
561
+ inputSchema={
562
+ "type": "object",
563
+ "properties": {
564
+ "connection": {
565
+ "type": "string",
566
+ "description": DATABASE_CONNECTION_NAME
567
+ },
568
+ "sql": {
569
+ "type": "string",
570
+ "description": "SQL query to analyze"
571
+ }
572
+ },
573
+ "required": ["connection", "sql"]
574
+ }
575
+ )
576
+ ]
577
+
578
+ async def _handle_list_tables(self, connection: str) -> list[types.TextContent]:
579
+ """处理列表表格工具调用
580
+
581
+ Args:
582
+ connection: 数据库连接名称
583
+
584
+ Returns:
585
+ list[types.TextContent]: 表格列表
586
+ """
587
+ async with self.get_handler(connection) as handler:
588
+ tables = await handler.get_tables()
589
+ if not tables:
590
+ # 空表列表的情况也返回数据库类型
591
+ return [types.TextContent(type="text", text=f"[{handler.db_type}] No tables found")]
592
+
593
+ formatted_tables = "\n".join([
594
+ f"Table: {table.name}\n" +
595
+ f"URI: {table.uri}\n" +
596
+ (f"Description: {table.description}\n" if table.description else "") +
597
+ "---"
598
+ for table in tables
599
+ ])
600
+ # 添加数据库类型前缀
601
+ return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{formatted_tables}")]
602
+
603
+ async def _handle_run_query(self, connection: str, sql: str) -> list[types.TextContent]:
604
+ """处理运行查询工具调用
605
+
606
+ Args:
607
+ connection: 数据库连接名称
608
+ sql: SQL查询语句
609
+
610
+ Returns:
611
+ list[types.TextContent]: 查询结果
612
+
613
+ Raises:
614
+ ConfigurationError: 如果SQL为空或非SELECT语句
615
+ """
616
+ if not sql:
617
+ raise ConfigurationError(EMPTY_QUERY_ERROR)
618
+
619
+ # Only allow SELECT statements
620
+ if not sql.lower().startswith("select"):
621
+ raise ConfigurationError(SELECT_ONLY_ERROR)
622
+
623
+ async with self.get_handler(connection) as handler:
624
+ result = await handler.execute_query(sql)
625
+ return [types.TextContent(type="text", text=result)]
626
+
627
+ async def _handle_table_tools(self, name: str, connection: str, table: str) -> list[types.TextContent]:
628
+ """处理表相关工具调用
629
+
630
+ Args:
631
+ name: 工具名称
632
+ connection: 数据库连接名称
633
+ table: 表名
634
+
635
+ Returns:
636
+ list[types.TextContent]: 工具执行结果
637
+
638
+ Raises:
639
+ ConfigurationError: 如果表名为空
640
+ """
641
+ if not table:
642
+ raise ConfigurationError(EMPTY_TABLE_NAME_ERROR)
643
+
644
+ async with self.get_handler(connection) as handler:
645
+ result = await handler.execute_tool_query(name, table_name=table)
646
+ return [types.TextContent(type="text", text=result)]
647
+
648
+ async def _handle_explain_query(self, connection: str, sql: str) -> list[types.TextContent]:
649
+ """处理解释查询工具调用
650
+
651
+ Args:
652
+ connection: 数据库连接名称
653
+ sql: SQL查询语句
654
+
655
+ Returns:
656
+ list[types.TextContent]: 查询解释
657
+
658
+ Raises:
659
+ ConfigurationError: 如果SQL为空
660
+ """
661
+ if not sql:
662
+ raise ConfigurationError(EMPTY_QUERY_ERROR)
663
+
664
+ async with self.get_handler(connection) as handler:
665
+ result = await handler.execute_tool_query("dbutils-explain-query", sql=sql)
666
+ return [types.TextContent(type="text", text=result)]
667
+
668
+ async def _handle_performance(self, connection: str) -> list[types.TextContent]:
669
+ """处理性能统计工具调用
670
+
671
+ Args:
672
+ connection: 数据库连接名称
673
+
674
+ Returns:
675
+ list[types.TextContent]: 性能统计
676
+ """
677
+ async with self.get_handler(connection) as handler:
678
+ performance_stats = handler.stats.get_performance_stats()
679
+ return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{performance_stats}")]
680
+
681
+ async def _handle_analyze_query(self, connection: str, sql: str) -> list[types.TextContent]:
682
+ """处理查询分析工具调用
683
+
684
+ Args:
685
+ connection: 数据库连接名称
686
+ sql: SQL查询语句
687
+
688
+ Returns:
689
+ list[types.TextContent]: 查询分析结果
690
+
691
+ Raises:
692
+ ConfigurationError: 如果SQL为空
693
+ """
694
+ if not sql:
695
+ raise ConfigurationError(EMPTY_QUERY_ERROR)
696
+
697
+ async with self.get_handler(connection) as handler:
698
+ # First get the execution plan
699
+ explain_result = await handler.explain_query(sql)
700
+
701
+ # Then execute the actual query to measure performance
702
+ start_time = datetime.now()
703
+ if sql.lower().startswith("select"):
351
704
  try:
352
- if hasattr(handler.stats, 'to_dict') and callable(handler.stats.to_dict):
353
- stats_dict = handler.stats.to_dict()
354
- stats_json = json.dumps(stats_dict)
355
- self.send_log(LOG_LEVEL_INFO, f"Resource stats: {stats_json}")
356
- else:
357
- self.send_log(LOG_LEVEL_INFO, "Resource stats not available")
358
- except TypeError:
359
- self.send_log(LOG_LEVEL_INFO, "Resource stats: [Could not serialize stats object]")
360
- yield handler
361
- except yaml.YAMLError as e:
362
- raise ConfigurationError(f"Invalid YAML configuration: {str(e)}")
363
- except ImportError as e:
364
- raise ConfigurationError(f"Failed to import handler for {db_type}: {str(e)}")
365
- finally:
366
- if handler:
367
- self.send_log(LOG_LEVEL_DEBUG, f"Cleaning up handler for {connection}")
368
- handler.stats.record_connection_end()
369
- # 使用通用的方式处理统计信息序列化
370
- try:
371
- if hasattr(handler.stats, 'to_dict') and callable(handler.stats.to_dict):
372
- stats_dict = handler.stats.to_dict()
373
- stats_json = json.dumps(stats_dict)
374
- self.send_log(LOG_LEVEL_INFO, f"Final resource stats: {stats_json}")
375
- else:
376
- self.send_log(LOG_LEVEL_INFO, "Final resource stats not available")
377
- except TypeError:
378
- self.send_log(LOG_LEVEL_INFO, "Final resource stats: [Could not serialize stats object]")
379
- # 清理资源
380
- if hasattr(handler, 'cleanup') and callable(handler.cleanup):
381
- await handler.cleanup()
705
+ await handler.execute_query(sql)
706
+ except Exception as e:
707
+ # If query fails, we still provide the execution plan
708
+ self.send_log(LOG_LEVEL_ERROR, f"Query execution failed during analysis: {str(e)}")
709
+ duration = (datetime.now() - start_time).total_seconds()
710
+
711
+ # Combine analysis results
712
+ analysis = [
713
+ f"[{handler.db_type}] Query Analysis",
714
+ f"SQL: {sql}",
715
+ "",
716
+ f"Execution Time: {duration*1000:.2f}ms",
717
+ "",
718
+ "Execution Plan:",
719
+ explain_result
720
+ ]
721
+
722
+ # Add optimization suggestions
723
+ suggestions = self._get_optimization_suggestions(explain_result, duration)
724
+ if suggestions:
725
+ analysis.append("\nOptimization Suggestions:")
726
+ analysis.extend(suggestions)
727
+
728
+ return [types.TextContent(type="text", text="\n".join(analysis))]
729
+
730
+ def _get_optimization_suggestions(self, explain_result: str, duration: float) -> list[str]:
731
+ """根据执行计划和耗时获取优化建议
732
+
733
+ Args:
734
+ explain_result: 执行计划
735
+ duration: 查询耗时(秒)
736
+
737
+ Returns:
738
+ list[str]: 优化建议列表
739
+ """
740
+ suggestions = []
741
+ if "seq scan" in explain_result.lower() and duration > 0.1:
742
+ suggestions.append("- Consider adding an index to avoid sequential scan")
743
+ if "hash join" in explain_result.lower() and duration > 0.5:
744
+ suggestions.append("- Consider optimizing join conditions")
745
+ if duration > 0.5: # 500ms
746
+ suggestions.append("- Query is slow, consider optimizing or adding caching")
747
+ if "temporary" in explain_result.lower():
748
+ suggestions.append("- Query creates temporary tables, consider restructuring")
749
+
750
+ return suggestions
382
751
 
383
752
  def _setup_handlers(self):
384
753
  """Setup MCP handlers"""
@@ -409,180 +778,7 @@ class ConnectionServer:
409
778
 
410
779
  @self.server.list_tools()
411
780
  async def handle_list_tools() -> list[types.Tool]:
412
- return [
413
- types.Tool(
414
- name="dbutils-run-query",
415
- description="Execute read-only SQL query on database connection",
416
- inputSchema={
417
- "type": "object",
418
- "properties": {
419
- "connection": {
420
- "type": "string",
421
- "description": DATABASE_CONNECTION_NAME
422
- },
423
- "sql": {
424
- "type": "string",
425
- "description": "SQL query (SELECT only)"
426
- }
427
- },
428
- "required": ["connection", "sql"]
429
- }
430
- ),
431
- types.Tool(
432
- name="dbutils-list-tables",
433
- description="List all available tables in the specified database connection",
434
- inputSchema={
435
- "type": "object",
436
- "properties": {
437
- "connection": {
438
- "type": "string",
439
- "description": DATABASE_CONNECTION_NAME
440
- }
441
- },
442
- "required": ["connection"]
443
- }
444
- ),
445
- types.Tool(
446
- name="dbutils-describe-table",
447
- description="Get detailed information about a table's structure",
448
- inputSchema={
449
- "type": "object",
450
- "properties": {
451
- "connection": {
452
- "type": "string",
453
- "description": DATABASE_CONNECTION_NAME
454
- },
455
- "table": {
456
- "type": "string",
457
- "description": "Table name to describe"
458
- }
459
- },
460
- "required": ["connection", "table"]
461
- }
462
- ),
463
- types.Tool(
464
- name="dbutils-get-ddl",
465
- description="Get DDL statement for creating the table",
466
- inputSchema={
467
- "type": "object",
468
- "properties": {
469
- "connection": {
470
- "type": "string",
471
- "description": DATABASE_CONNECTION_NAME
472
- },
473
- "table": {
474
- "type": "string",
475
- "description": "Table name to get DDL for"
476
- }
477
- },
478
- "required": ["connection", "table"]
479
- }
480
- ),
481
- types.Tool(
482
- name="dbutils-list-indexes",
483
- description="List all indexes on the specified table",
484
- inputSchema={
485
- "type": "object",
486
- "properties": {
487
- "connection": {
488
- "type": "string",
489
- "description": DATABASE_CONNECTION_NAME
490
- },
491
- "table": {
492
- "type": "string",
493
- "description": "Table name to list indexes for"
494
- }
495
- },
496
- "required": ["connection", "table"]
497
- }
498
- ),
499
- types.Tool(
500
- name="dbutils-get-stats",
501
- description="Get table statistics like row count and size",
502
- inputSchema={
503
- "type": "object",
504
- "properties": {
505
- "connection": {
506
- "type": "string",
507
- "description": DATABASE_CONNECTION_NAME
508
- },
509
- "table": {
510
- "type": "string",
511
- "description": "Table name to get statistics for"
512
- }
513
- },
514
- "required": ["connection", "table"]
515
- }
516
- ),
517
- types.Tool(
518
- name="dbutils-list-constraints",
519
- description="List all constraints (primary key, foreign keys, etc) on the table",
520
- inputSchema={
521
- "type": "object",
522
- "properties": {
523
- "connection": {
524
- "type": "string",
525
- "description": DATABASE_CONNECTION_NAME
526
- },
527
- "table": {
528
- "type": "string",
529
- "description": "Table name to list constraints for"
530
- }
531
- },
532
- "required": ["connection", "table"]
533
- }
534
- ),
535
- types.Tool(
536
- name="dbutils-explain-query",
537
- description="Get execution plan for a SQL query",
538
- inputSchema={
539
- "type": "object",
540
- "properties": {
541
- "connection": {
542
- "type": "string",
543
- "description": DATABASE_CONNECTION_NAME
544
- },
545
- "sql": {
546
- "type": "string",
547
- "description": "SQL query to explain"
548
- }
549
- },
550
- "required": ["connection", "sql"]
551
- }
552
- ),
553
- types.Tool(
554
- name="dbutils-get-performance",
555
- description="Get database performance statistics",
556
- inputSchema={
557
- "type": "object",
558
- "properties": {
559
- "connection": {
560
- "type": "string",
561
- "description": DATABASE_CONNECTION_NAME
562
- }
563
- },
564
- "required": ["connection"]
565
- }
566
- ),
567
- types.Tool(
568
- name="dbutils-analyze-query",
569
- description="Analyze a SQL query for performance",
570
- inputSchema={
571
- "type": "object",
572
- "properties": {
573
- "connection": {
574
- "type": "string",
575
- "description": DATABASE_CONNECTION_NAME
576
- },
577
- "sql": {
578
- "type": "string",
579
- "description": "SQL query to analyze"
580
- }
581
- },
582
- "required": ["connection", "sql"]
583
- }
584
- )
585
- ]
781
+ return self._get_available_tools()
586
782
 
587
783
  @self.server.call_tool()
588
784
  async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
@@ -592,100 +788,22 @@ class ConnectionServer:
592
788
  connection = arguments["connection"]
593
789
 
594
790
  if name == "dbutils-list-tables":
595
- async with self.get_handler(connection) as handler:
596
- tables = await handler.get_tables()
597
- if not tables:
598
- # 空表列表的情况也返回数据库类型
599
- return [types.TextContent(type="text", text=f"[{handler.db_type}] No tables found")]
600
-
601
- formatted_tables = "\n".join([
602
- f"Table: {table.name}\n" +
603
- f"URI: {table.uri}\n" +
604
- (f"Description: {table.description}\n" if table.description else "") +
605
- "---"
606
- for table in tables
607
- ])
608
- # 添加数据库类型前缀
609
- return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{formatted_tables}")]
791
+ return await self._handle_list_tables(connection)
610
792
  elif name == "dbutils-run-query":
611
793
  sql = arguments.get("sql", "").strip()
612
- if not sql:
613
- raise ConfigurationError(EMPTY_QUERY_ERROR)
614
-
615
- # Only allow SELECT statements
616
- if not sql.lower().startswith("select"):
617
- raise ConfigurationError(SELECT_ONLY_ERROR)
618
-
619
- async with self.get_handler(connection) as handler:
620
- result = await handler.execute_query(sql)
621
- return [types.TextContent(type="text", text=result)]
794
+ return await self._handle_run_query(connection, sql)
622
795
  elif name in ["dbutils-describe-table", "dbutils-get-ddl", "dbutils-list-indexes",
623
796
  "dbutils-get-stats", "dbutils-list-constraints"]:
624
797
  table = arguments.get("table", "").strip()
625
- if not table:
626
- raise ConfigurationError(EMPTY_TABLE_NAME_ERROR)
627
-
628
- async with self.get_handler(connection) as handler:
629
- result = await handler.execute_tool_query(name, table_name=table)
630
- return [types.TextContent(type="text", text=result)]
798
+ return await self._handle_table_tools(name, connection, table)
631
799
  elif name == "dbutils-explain-query":
632
800
  sql = arguments.get("sql", "").strip()
633
- if not sql:
634
- raise ConfigurationError(EMPTY_QUERY_ERROR)
635
-
636
- async with self.get_handler(connection) as handler:
637
- result = await handler.execute_tool_query(name, sql=sql)
638
- return [types.TextContent(type="text", text=result)]
801
+ return await self._handle_explain_query(connection, sql)
639
802
  elif name == "dbutils-get-performance":
640
- async with self.get_handler(connection) as handler:
641
- performance_stats = handler.stats.get_performance_stats()
642
- return [types.TextContent(type="text", text=f"[{handler.db_type}]\n{performance_stats}")]
803
+ return await self._handle_performance(connection)
643
804
  elif name == "dbutils-analyze-query":
644
805
  sql = arguments.get("sql", "").strip()
645
- if not sql:
646
- raise ConfigurationError(EMPTY_QUERY_ERROR)
647
-
648
- async with self.get_handler(connection) as handler:
649
- # First get the execution plan
650
- explain_result = await handler.explain_query(sql)
651
-
652
- # Then execute the actual query to measure performance
653
- start_time = datetime.now()
654
- if sql.lower().startswith("select"):
655
- try:
656
- await handler.execute_query(sql)
657
- except Exception as e:
658
- # If query fails, we still provide the execution plan
659
- self.send_log(LOG_LEVEL_ERROR, f"Query execution failed during analysis: {str(e)}")
660
- duration = (datetime.now() - start_time).total_seconds()
661
-
662
- # Combine analysis results
663
- analysis = [
664
- f"[{handler.db_type}] Query Analysis",
665
- f"SQL: {sql}",
666
- "",
667
- f"Execution Time: {duration*1000:.2f}ms",
668
- "",
669
- "Execution Plan:",
670
- explain_result
671
- ]
672
-
673
- # Add optimization suggestions based on execution plan and timing
674
- suggestions = []
675
- if "seq scan" in explain_result.lower() and duration > 0.1:
676
- suggestions.append("- Consider adding an index to avoid sequential scan")
677
- if "hash join" in explain_result.lower() and duration > 0.5:
678
- suggestions.append("- Consider optimizing join conditions")
679
- if duration > 0.5: # 500ms
680
- suggestions.append("- Query is slow, consider optimizing or adding caching")
681
- if "temporary" in explain_result.lower():
682
- suggestions.append("- Query creates temporary tables, consider restructuring")
683
-
684
- if suggestions:
685
- analysis.append("\nOptimization Suggestions:")
686
- analysis.extend(suggestions)
687
-
688
- return [types.TextContent(type="text", text="\n".join(analysis))]
806
+ return await self._handle_analyze_query(connection, sql)
689
807
  else:
690
808
  raise ConfigurationError(f"Unknown tool: {name}")
691
809