This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch agent/test-012-real-doris-errors in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit 00dd757645a97025e67372f7f0b92086fbdac3cf Author: FreeOnePlus <[email protected]> AuthorDate: Wed Jul 29 18:38:55 2026 +0800 fix: release analysis tool connections --- doris_mcp_server/utils/analysis_tools.py | 8 +++ doris_mcp_server/utils/data_governance_tools.py | 9 +++ doris_mcp_server/utils/security_analytics_tools.py | 9 +++ test/integration/test_real_doris_transports.py | 79 ++++++++++++++++++++++ test/tools/test_tools_operation_guard.py | 49 ++++++++++++++ 5 files changed, 154 insertions(+) diff --git a/doris_mcp_server/utils/analysis_tools.py b/doris_mcp_server/utils/analysis_tools.py index 4f63f54..d50f3d3 100644 --- a/doris_mcp_server/utils/analysis_tools.py +++ b/doris_mcp_server/utils/analysis_tools.py @@ -821,6 +821,14 @@ class SQLAnalyzer: "catalog": catalog_name, "timestamp": time.strftime('%Y-%m-%d %H:%M:%S') } + finally: + release_connection = getattr( + self.connection_manager, + "release_connection", + None, + ) + if callable(release_connection): + await release_connection("query", connection) except Exception as e: logger.error(f"SQL PROFILE failed: {str(e)}") diff --git a/doris_mcp_server/utils/data_governance_tools.py b/doris_mcp_server/utils/data_governance_tools.py index 4e3238d..8887769 100644 --- a/doris_mcp_server/utils/data_governance_tools.py +++ b/doris_mcp_server/utils/data_governance_tools.py @@ -160,6 +160,7 @@ class DataGovernanceTools: catalog_name: Catalog name db_name: Database name """ + connection = None try: start_time = time.time() if time_threshold_hours is None: @@ -221,6 +222,14 @@ class DataGovernanceTools: "error": str(e), "monitoring_timestamp": datetime.now().isoformat() } + finally: + release_connection = getattr( + self.connection_manager, + "release_connection", + None, + ) + if connection is not None and callable(release_connection): + await release_connection("query", connection) # ==================== Private Helper Methods ==================== diff --git a/doris_mcp_server/utils/security_analytics_tools.py b/doris_mcp_server/utils/security_analytics_tools.py index 8c52c62..abbf78d 100644 --- a/doris_mcp_server/utils/security_analytics_tools.py +++ b/doris_mcp_server/utils/security_analytics_tools.py @@ -55,6 +55,7 @@ class SecurityAnalyticsTools: Returns: Comprehensive access pattern analysis """ + connection = None try: start_time = time.time() @@ -159,6 +160,14 @@ class SecurityAnalyticsTools: "error": str(e), "analysis_timestamp": datetime.now().isoformat() } + finally: + release_connection = getattr( + self.connection_manager, + "release_connection", + None, + ) + if connection is not None and callable(release_connection): + await release_connection("query", connection) # ==================== Private Helper Methods ==================== diff --git a/test/integration/test_real_doris_transports.py b/test/integration/test_real_doris_transports.py index 6135156..b875722 100644 --- a/test/integration/test_real_doris_transports.py +++ b/test/integration/test_real_doris_transports.py @@ -409,3 +409,82 @@ async def test_real_doris_read_write_permission_timeout_and_recovery( assert denied_result.is_error is True assert denied_payload["success"] is False assert denied_payload["error_type"] == "permission_denied" + + [email protected]("transport", ["http", "stdio"]) +async def test_real_doris_tool_regression_paths( + transport: str, + doris_sandbox: DorisSandbox, +) -> None: + environment = _server_environment( + doris_sandbox.settings, + user=doris_sandbox.settings.user, + password=doris_sandbox.settings.password, + ) + missing_table = f"{doris_sandbox.table}_missing" + + async with _transport_client(transport, environment) as client: + profile_result = await client.call_tool( + "get_sql_profile", + { + "sql": f"SELECT COUNT(*) AS row_count FROM {doris_sandbox.qualified_table}", + "db_name": doris_sandbox.settings.database, + }, + ) + assert isinstance(profile_result.structured_content, dict) + profile_payload = profile_result.structured_content + assert isinstance(profile_payload["success"], bool) + assert profile_payload["trace_id"] + assert isinstance(profile_payload["execution_time"], int | float) + assert "auth_context" not in str(profile_payload.get("error", "")) + assert "referenced before assignment" not in str( + profile_payload.get("error", "") + ) + + freshness_result = await client.call_tool( + "monitor_data_freshness", + { + "table_names": [missing_table], + "db_name": doris_sandbox.settings.database, + }, + ) + assert freshness_result.is_error is False + assert isinstance(freshness_result.structured_content, dict) + freshness_payload = freshness_result.structured_content + assert freshness_payload["monitoring_scope"]["time_threshold_hours"] == 24 + assert freshness_payload["table_freshness"][missing_table] == { + "last_update": None, + "staleness_hours": None, + "freshness_score": 0.0, + "status": "unknown", + "method_used": "none", + "error": "Unable to determine last update time", + } + assert freshness_payload["data_flow_issues"] == [] + + access_result = await client.call_tool( + "analyze_data_access_patterns", + { + "days": 1, + "include_system_users": True, + "min_query_threshold": 1, + }, + ) + assert access_result.is_error is False + assert isinstance(access_result.structured_content, dict) + access_payload = access_result.structured_content + assert "error" not in access_payload + role_analysis = access_payload["role_analysis"] + assert role_analysis + assert any( + doris_sandbox.settings.user in role["users"] + for role in role_analysis.values() + ) + + recovered_result, recovered_payload = await _exec_query( + client, + "SELECT 1 AS recovered", + ) + assert recovered_result.is_error is False + assert recovered_payload["success"] is True + assert recovered_payload["data"][0]["recovered"] == 1 diff --git a/test/tools/test_tools_operation_guard.py b/test/tools/test_tools_operation_guard.py index 3641c26..1deedb7 100644 --- a/test/tools/test_tools_operation_guard.py +++ b/test/tools/test_tools_operation_guard.py @@ -480,6 +480,8 @@ async def test_sql_profile_binds_auth_context_without_catalog(tmp_path, db_name) ): assert call["sql"].startswith(sql_prefix) assert call["doris_user"] == "alice" + assert connection_manager.connection_acquires == 1 + assert connection_manager.connection_releases == 1 @pytest.mark.asyncio @@ -521,6 +523,30 @@ async def test_unknown_data_freshness_is_not_compared_as_a_number(): assert issues == [] [email protected] +async def test_data_freshness_releases_query_connection(): + connection = SimpleNamespace() + connection_manager = SimpleNamespace( + get_connection=AsyncMock(return_value=connection), + release_connection=AsyncMock(), + ) + governance = DataGovernanceTools(connection_manager) + governance._analyze_table_freshness = AsyncMock( + return_value={ + "status": "unknown", + "staleness_hours": None, + } + ) + + result = await governance.monitor_data_freshness(tables=["orders"]) + + assert result["table_freshness"]["orders"]["status"] == "unknown" + connection_manager.release_connection.assert_awaited_once_with( + "query", + connection, + ) + + @pytest.mark.asyncio async def test_doris4_user_roles_use_show_grants_metadata(): connection = Doris4RoleMetadataConnection() @@ -536,6 +562,29 @@ async def test_doris4_user_roles_use_show_grants_metadata(): assert connection.calls == ["SHOW ALL GRANTS"] [email protected] +async def test_data_access_patterns_releases_query_connection(): + connection = SimpleNamespace() + connection_manager = SimpleNamespace( + get_connection=AsyncMock(return_value=connection), + release_connection=AsyncMock(), + ) + analytics = SecurityAnalyticsTools(connection_manager) + analytics._get_audit_log_data = AsyncMock(return_value=[{"user_name": "root"}]) + analytics._analyze_user_access_patterns = AsyncMock(return_value=[]) + analytics._analyze_role_access_patterns = AsyncMock(return_value={}) + analytics._detect_security_anomalies = AsyncMock(return_value=[]) + analytics._generate_access_insights = AsyncMock(return_value={}) + + result = await analytics.analyze_data_access_patterns() + + assert "error" not in result + connection_manager.release_connection.assert_awaited_once_with( + "query", + connection, + ) + + @pytest.mark.asyncio async def test_user_roles_fall_back_to_current_user_grants(): connection = CurrentUserRoleMetadataConnection() --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
