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 98a8cc0 feat: add fail-closed Doris version capability probing (#164)
98a8cc0 is described below
commit 98a8cc025741cd197e892bad4eefaabee364f9a4
Author: Yijia Su <[email protected]>
AuthorDate: Fri Jul 31 01:05:42 2026 +0800
feat: add fail-closed Doris version capability probing (#164)
Establish the deterministic Doris version capability foundation for the
1.0.0 hierarchical tool architecture.
---
doris_mcp_server/tools/doris_version.py | 195 ++++++++++++++++++++++++++++++++
test/tools/test_doris_version.py | 170 ++++++++++++++++++++++++++++
2 files changed, 365 insertions(+)
diff --git a/doris_mcp_server/tools/doris_version.py
b/doris_mcp_server/tools/doris_version.py
new file mode 100644
index 0000000..f8aef78
--- /dev/null
+++ b/doris_mcp_server/tools/doris_version.py
@@ -0,0 +1,195 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Fail-closed Apache Doris version probing, parsing, and comparison."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping, Sequence
+from dataclasses import dataclass
+from enum import StrEnum
+from typing import Any
+
+from ..utils.db import DorisConnection
+
+DORIS_VERSION_COMMENT_QUERY = "SELECT @@version_comment;"
+_VERSION_COMMENT_COLUMN = "@@version_comment"
+_VERSION_COMMENT_MAX_BYTES = 4096
+
+_VERSION_PATTERN = re.compile(
+ r"""
+ (?<![A-Za-z0-9_])
+ (?:apache\s+)?doris
+ (?:\s*,?\s*version)?
+ (?:\s+doris-|\s*-\s*|\s+)
+ (?P<core>\d+\.\d+\.\d+)
+ (?:-(?P<prerelease>rc\d+|alpha\d*|beta\d*))?
+ (?:-(?P<commit>[0-9a-f]{7,40}))?
+ (?=\s|\(|,|$)
+ """,
+ re.IGNORECASE | re.VERBOSE,
+)
+_PRERELEASE_PATTERN = re.compile(
+ r"(?P<kind>alpha|beta|rc)(?P<number>\d*)",
+ re.IGNORECASE,
+)
+_DEPLOYMENT_PATTERNS = (
+ ("cloud", re.compile(r"\bcloud\s+mode\b", re.IGNORECASE)),
+ ("shared_data", re.compile(r"\bshared[-\s]+data\b", re.IGNORECASE)),
+ ("shared_nothing", re.compile(r"\bshared[-\s]+nothing\b", re.IGNORECASE)),
+)
+_PRERELEASE_RANK = {"alpha": 0, "beta": 1, "rc": 2}
+_NORMALIZED_PRERELEASE = {"alpha": "a", "beta": "b", "rc": "rc"}
+
+
+class DorisVersionParseStatus(StrEnum):
+ PARSED = "parsed"
+ UNKNOWN = "unknown"
+
+
+@dataclass(frozen=True, slots=True)
+class DorisVersion:
+ raw: str
+ major: int | None = None
+ minor: int | None = None
+ patch: int | None = None
+ prerelease: str | None = None
+ commit: str | None = None
+ deployment_hint: str | None = None
+ parse_status: DorisVersionParseStatus = DorisVersionParseStatus.UNKNOWN
+
+ @property
+ def is_parsed(self) -> bool:
+ return self.parse_status is DorisVersionParseStatus.PARSED
+
+ @property
+ def core(self) -> str | None:
+ if (
+ not self.is_parsed
+ or self.major is None
+ or self.minor is None
+ or self.patch is None
+ ):
+ return None
+ return f"{self.major}.{self.minor}.{self.patch}"
+
+ @property
+ def normalized(self) -> str | None:
+ core = self.core
+ if core is None or self.prerelease is None:
+ return core
+
+ prerelease = _parse_prerelease(self.prerelease)
+ kind, number = prerelease
+ return f"{core}{_NORMALIZED_PRERELEASE[kind]}{number}"
+
+ def compare(self, other: DorisVersion) -> int:
+ left = self._comparison_key()
+ right = other._comparison_key()
+ return (left > right) - (left < right)
+
+ def is_at_least(self, other: DorisVersion) -> bool:
+ return self.compare(other) >= 0
+
+ def _comparison_key(self) -> tuple[int, int, int, int, int]:
+ if (
+ not self.is_parsed
+ or self.major is None
+ or self.minor is None
+ or self.patch is None
+ ):
+ raise ValueError("Cannot compare an unparsed Doris version")
+
+ if self.prerelease is None:
+ prerelease_rank = len(_PRERELEASE_RANK)
+ prerelease_number = 0
+ else:
+ kind, prerelease_number = _parse_prerelease(self.prerelease)
+ prerelease_rank = _PRERELEASE_RANK[kind]
+
+ return (
+ self.major,
+ self.minor,
+ self.patch,
+ prerelease_rank,
+ prerelease_number,
+ )
+
+
+def parse_doris_version_comment(comment: str) -> DorisVersion:
+ deployment_hint = _detect_deployment_hint(comment)
+ match = _VERSION_PATTERN.search(comment)
+ if match is None:
+ return DorisVersion(raw=comment, deployment_hint=deployment_hint)
+
+ major, minor, patch = (int(part) for part in
match.group("core").split("."))
+ prerelease = match.group("prerelease")
+ commit = match.group("commit")
+
+ return DorisVersion(
+ raw=comment,
+ major=major,
+ minor=minor,
+ patch=patch,
+ prerelease=prerelease.lower() if prerelease else None,
+ commit=commit.lower() if commit else None,
+ deployment_hint=deployment_hint,
+ parse_status=DorisVersionParseStatus.PARSED,
+ )
+
+
+def parse_doris_version_rows(
+ rows: Sequence[Mapping[str, Any]],
+) -> DorisVersion:
+ if not rows:
+ return DorisVersion(raw="")
+
+ for key, value in rows[0].items():
+ if key.strip().casefold() != _VERSION_COMMENT_COLUMN:
+ continue
+ if isinstance(value, str):
+ return parse_doris_version_comment(value)
+ return DorisVersion(raw="")
+
+ return DorisVersion(raw="")
+
+
+async def probe_doris_version(connection: DorisConnection) -> DorisVersion:
+ result = await connection.execute(
+ DORIS_VERSION_COMMENT_QUERY,
+ mask_result=False,
+ max_rows=1,
+ max_bytes=_VERSION_COMMENT_MAX_BYTES,
+ )
+ return parse_doris_version_rows(result.data)
+
+
+def _parse_prerelease(value: str) -> tuple[str, int]:
+ match = _PRERELEASE_PATTERN.fullmatch(value)
+ if match is None:
+ raise ValueError(f"Unsupported Doris prerelease: {value}")
+ kind = match.group("kind").lower()
+ number = int(match.group("number") or "0")
+ return kind, number
+
+
+def _detect_deployment_hint(comment: str) -> str | None:
+ for name, pattern in _DEPLOYMENT_PATTERNS:
+ if pattern.search(comment):
+ return name
+ return None
diff --git a/test/tools/test_doris_version.py b/test/tools/test_doris_version.py
new file mode 100644
index 0000000..f349d13
--- /dev/null
+++ b/test/tools/test_doris_version.py
@@ -0,0 +1,170 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from unittest.mock import AsyncMock, MagicMock
+
+import pytest
+
+from doris_mcp_server.tools.doris_version import (
+ DORIS_VERSION_COMMENT_QUERY,
+ DorisVersionParseStatus,
+ parse_doris_version_comment,
+ parse_doris_version_rows,
+ probe_doris_version,
+)
+from doris_mcp_server.utils.db import DorisConnection, QueryResult
+
+
+def test_version_probe_uses_version_comment() -> None:
+ assert DORIS_VERSION_COMMENT_QUERY == "SELECT @@version_comment;"
+
+
[email protected](
+ ("comment", "core", "prerelease", "commit", "deployment_hint",
"normalized"),
+ [
+ (
+ "Doris version doris-3.0.3-rc03-43f06a5e26 (Cloud Mode)",
+ "3.0.3",
+ "rc03",
+ "43f06a5e26",
+ "cloud",
+ "3.0.3rc3",
+ ),
+ (
+ "Apache Doris version 4.0.7",
+ "4.0.7",
+ None,
+ None,
+ None,
+ "4.0.7",
+ ),
+ (
+ "doris-4.1.3-abcdef1234",
+ "4.1.3",
+ None,
+ "abcdef1234",
+ None,
+ "4.1.3",
+ ),
+ ],
+)
+def test_parse_supported_version_comments(
+ comment: str,
+ core: str,
+ prerelease: str | None,
+ commit: str | None,
+ deployment_hint: str | None,
+ normalized: str,
+) -> None:
+ version = parse_doris_version_comment(comment)
+
+ assert version.parse_status is DorisVersionParseStatus.PARSED
+ assert version.core == core
+ assert version.prerelease == prerelease
+ assert version.commit == commit
+ assert version.deployment_hint == deployment_hint
+ assert version.normalized == normalized
+ assert version.raw == comment
+
+
[email protected](
+ "comment",
+ [
+ "",
+ "MySQL 8.0.36",
+ "version 4.1.3",
+ "Doris version unknown",
+ "Doris version 4.1.3-preview1",
+ "Doris version 4.1",
+ ],
+)
+def test_unknown_version_comments_fail_closed(comment: str) -> None:
+ version = parse_doris_version_comment(comment)
+
+ assert version.parse_status is DorisVersionParseStatus.UNKNOWN
+ assert version.is_parsed is False
+ assert version.core is None
+ assert version.normalized is None
+
+
+def test_comparison_ignores_commit_and_orders_prereleases() -> None:
+ alpha = parse_doris_version_comment("Doris version 3.0.3-alpha1")
+ beta = parse_doris_version_comment("Doris version 3.0.3-beta2")
+ release_candidate = parse_doris_version_comment("Doris version 3.0.3-rc03")
+ stable_a = parse_doris_version_comment("Doris version 3.0.3-43f06a5e26")
+ stable_b = parse_doris_version_comment("Doris version 3.0.3-abcdef1234")
+
+ assert beta.compare(alpha) > 0
+ assert release_candidate.compare(beta) > 0
+ assert stable_a.compare(release_candidate) > 0
+ assert stable_a.compare(stable_b) == 0
+ assert stable_a.is_at_least(release_candidate) is True
+
+
+def test_unparsed_version_cannot_be_compared() -> None:
+ unknown = parse_doris_version_comment("Doris version unknown")
+ stable = parse_doris_version_comment("Doris version 4.1.3")
+
+ with pytest.raises(ValueError, match="unparsed Doris version"):
+ unknown.compare(stable)
+
+
[email protected](
+ "rows",
+ [
+ [],
+ [{"version_comment": "Doris version 4.1.3"}],
+ [{"@@version_comment": None}],
+ ],
+)
+def test_version_rows_fail_closed_without_expected_string_column(
+ rows: list[dict[str, object]],
+) -> None:
+ version = parse_doris_version_rows(rows)
+
+ assert version.parse_status is DorisVersionParseStatus.UNKNOWN
+
+
+async def test_probe_executes_exact_read_only_query() -> None:
+ connection = MagicMock(spec=DorisConnection)
+ connection.execute = AsyncMock(
+ return_value=QueryResult(
+ data=[
+ {
+ "@@version_comment": (
+ "Doris version doris-3.0.3-rc03-43f06a5e26 "
+ "(Cloud Mode)"
+ )
+ }
+ ],
+ metadata={},
+ execution_time=0.01,
+ row_count=1,
+ sql=DORIS_VERSION_COMMENT_QUERY,
+ )
+ )
+
+ version = await probe_doris_version(connection)
+
+ assert version.normalized == "3.0.3rc3"
+ assert version.deployment_hint == "cloud"
+ connection.execute.assert_awaited_once_with(
+ DORIS_VERSION_COMMENT_QUERY,
+ mask_result=False,
+ max_rows=1,
+ max_bytes=4096,
+ )
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]