This is an automated email from the ASF dual-hosted git repository.

FreeOnePlus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git


The following commit(s) were added to refs/heads/master by this push:
     new f25b10b  fix: release analysis tool connections (#103)
f25b10b is described below

commit f25b10b1bf47a1da12a1fa0a0280d3645268ebe5
Author: Yijia Su <[email protected]>
AuthorDate: Wed Jul 29 18:40:36 2026 +0800

    fix: release analysis tool connections (#103)
---
 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]

Reply via email to