Source code for benchbox.platforms.clickhouse.adapter
"""Primary adapter for ClickHouse platforms."""
from __future__ import annotations
import logging
from benchbox.platforms.base import DriverIsolationCapability, PlatformAdapter
from benchbox.utils.dependencies import check_platform_dependencies, get_dependency_error_message
from .deployment_mode import CLICKHOUSE_DEPLOYMENT_MODE_VALUES, resolve_clickhouse_deployment_mode
from .diagnostics import ClickHouseDiagnosticsMixin
from .metadata import ClickHouseMetadataMixin
from .setup import ClickHouseSetupMixin
from .tuning import ClickHouseTuningMixin
from .workload import ClickHouseWorkloadMixin
logger = logging.getLogger(__name__)
[docs]
class ClickHouseAdapter(
ClickHouseMetadataMixin,
ClickHouseSetupMixin,
ClickHouseDiagnosticsMixin,
ClickHouseWorkloadMixin,
ClickHouseTuningMixin,
PlatformAdapter,
):
"""High-level adapter coordinating ClickHouse operations.
Known Limitations:
TPC-DS queries with known incompatibilities:
- Query 14: INTERSECT DISTINCT requires manual alias addition
- Query 30: Query plan cloning not implemented for aggregation steps (Code: 48)
- Query 81: Query plan cloning not implemented for aggregation steps (Code: 48)
These queries may fail even with transformations applied and require
manual query rewriting or ClickHouse engine improvements.
"""
plan_capture_phase_eligible = True
driver_isolation_capability = DriverIsolationCapability.NOT_FEASIBLE
# Known incompatible queries that may fail despite transformations
KNOWN_INCOMPATIBLE_QUERIES = {
"tpcds": [14, 30, 81],
}
[docs]
def __init__(self, **config):
super().__init__(**config)
self._dialect = "clickhouse"
# Determine deployment mode (from factory via colon syntax: clickhouse:local).
# Default to local mode for easiest onboarding (no credentials required).
is_cloud_subclass = config.get("_is_cloud_subclass", False)
self.deployment_mode = resolve_clickhouse_deployment_mode(config, allow_cloud=is_cloud_subclass)
# Validate deployment mode
# Note: "cloud" mode is now a separate first-class platform: clickhouse-cloud
# The _is_cloud_subclass flag is set by ClickHouseCloudAdapter to bypass this check
valid_modes = set(CLICKHOUSE_DEPLOYMENT_MODE_VALUES)
# Cloud mode is valid when called from ClickHouseCloudAdapter
if is_cloud_subclass:
valid_modes.add("cloud")
if self.deployment_mode not in valid_modes:
raise ValueError(
f"Invalid ClickHouse deployment mode '{self.deployment_mode}'. "
f"Valid modes: {', '.join(sorted(valid_modes))}"
)
# Mode-specific validation and setup
if self.deployment_mode == "server":
available, missing = check_platform_dependencies("clickhouse", ["clickhouse-driver"])
if not available:
error_msg = get_dependency_error_message("clickhouse", missing)
raise ImportError(error_msg)
self._setup_server_mode(config)
elif self.deployment_mode == "local":
import importlib.util
if importlib.util.find_spec("chdb") is None:
raise ImportError(
"ClickHouse local mode requires chDB but it is not installed.\n"
"To resolve this issue:\n"
" 1. Install chDB: uv add chdb\n"
" 2. Or switch to server mode: --platform clickhouse:server\n"
" 3. Or use ClickHouse Cloud: --platform clickhouse-cloud\n"
" 4. Or use a different platform (e.g., DuckDB)\n"
"\nFor more information about chDB, visit: https://github.com/chdb-io/chdb"
)
self._setup_local_mode(config)
elif self.deployment_mode == "cloud":
# Cloud mode is only valid when called from ClickHouseCloudAdapter
# Check for clickhouse-connect dependency
import importlib.util
if importlib.util.find_spec("clickhouse_connect") is None:
raise ImportError(
"ClickHouse Cloud requires clickhouse-connect but it is not installed.\n"
"To resolve this issue:\n"
" 1. Install ClickHouse Cloud extra: uv add benchbox --extra clickhouse-cloud\n"
" 2. Or use local mode: --platform clickhouse-local\n"
"\nFor more information, visit: https://clickhouse.com/docs/en/integrations/python"
)
self._setup_cloud_mode(config)
[docs]
def get_query_plan(self, connection, query: str) -> str | None:
"""Get the ClickHouse logical plan via ``EXPLAIN PLAN``.
All three modes (local/chDB, server/clickhouse-driver, cloud/
clickhouse-connect) expose the same ``connection.execute()`` API returning
indexable rows, so a single base-class implementation works for all of
them. ``EXPLAIN PLAN`` (not ``PIPELINE``) is used because it carries the
logical operator names needed for cross-platform comparison.
Returns the joined plan text, or ``None`` on any failure (e.g. ClickHouse
< 20.6 without EXPLAIN support), so capture degrades gracefully.
"""
try:
result = connection.execute(f"EXPLAIN PLAN {query}")
if not result:
return None
lines = [str(row[0]) if isinstance(row, (list, tuple)) else str(row) for row in result]
text = "\n".join(lines)
return text or None
except Exception as e:
self.logger.debug(f"Could not get ClickHouse query plan: {e}")
return None
[docs]
def get_query_plan_parser(self):
"""Return the ClickHouse plan parser (shared by local, server, and cloud)."""
from benchbox.core.query_plans.parsers.clickhouse import ClickHouseQueryPlanParser
return ClickHouseQueryPlanParser()
__all__ = ["ClickHouseAdapter"]