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]