Source code for alchemiq.clickhouse.ddl

"""DDL helpers: create/drop CH tables and run OPTIMIZE on a model's table."""

from __future__ import annotations

from typing import Any

from clickhouse_sqlalchemy.drivers.base import ClickHouseDialect  # ty: ignore[unresolved-import]
from sqlalchemy.schema import CreateColumn

from alchemiq.clickhouse.connection import get_clickhouse_client
from alchemiq.clickhouse.model import ch_engine_of
from alchemiq.clickhouse.registry import ch_metadata

_DIALECT = ClickHouseDialect()


def _column_defs(model: type) -> str:
    table = model.__table__  # ty: ignore[unresolved-attribute]
    rendered = [str(CreateColumn(c).compile(dialect=_DIALECT)) for c in table.columns]
    return ",\n  ".join(rendered)


def create_table_sql(model: type) -> str:
    """Return the CREATE TABLE IF NOT EXISTS DDL string for *model*."""
    table = model.__table__  # ty: ignore[unresolved-attribute]
    engine = ch_engine_of(model)
    return (
        f"CREATE TABLE IF NOT EXISTS {table.name} (\n  "
        f"{_column_defs(model)}\n) {engine.engine_clause()}"
    )


[docs] async def create_clickhouse_tables() -> None: """Create all registered CH tables using ``CREATE TABLE IF NOT EXISTS``. Tables are created in ``ch_metadata`` dependency order. Safe to call on every startup - existing tables are left unchanged. .. seealso:: :func:`.drop_clickhouse_tables` - tear down all CH tables. """ client = await get_clickhouse_client() for table in ch_metadata.sorted_tables: model = _model_for(table) await client.command(create_table_sql(model))
[docs] async def drop_clickhouse_tables() -> None: """Drop all registered CH tables using ``DROP TABLE IF EXISTS``. Tables are dropped in reverse ``ch_metadata`` dependency order. .. seealso:: :func:`.create_clickhouse_tables` - recreate the tables. """ client = await get_clickhouse_client() for table in reversed(ch_metadata.sorted_tables): await client.command(f"DROP TABLE IF EXISTS {table.name}")
[docs] async def optimize(model: type, *, cleanup: bool = False) -> None: """Run ``OPTIMIZE TABLE FINAL [CLEANUP]`` on *model*'s table. :param model: A :class:`.ClickHouseModel` subclass whose table to optimize. :param cleanup: If ``True``, appends ``CLEANUP`` to instruct ``ReplacingMergeTree`` to physically remove rows where ``is_deleted=1`` once merged. Requires ``allow_experimental_replacing_merge_with_cleanup`` enabled on the CH server. .. seealso:: :meth:`.ClickHouseRepository.cleanup` - higher-level soft-delete cleanup. """ client = await get_clickhouse_client() table = model.__table__ # ty: ignore[unresolved-attribute] sql = f"OPTIMIZE TABLE {table.name} FINAL" + (" CLEANUP" if cleanup else "") await client.command(sql)
def _model_for(table: Any) -> type: for mapper in _ch_metadata_mappers(): if mapper.local_table is table: return mapper.class_ raise LookupError(f"No CH model mapped to table {table.name!r}") def _ch_metadata_mappers() -> Any: from alchemiq.clickhouse.registry import ch_mapper_registry return list(ch_mapper_registry.mappers)