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]

Reply via email to