Skip to content

Columns and types

Column-level Meta

Use CHColumnMeta inner classes on the model, named after the column:

from dbwarden.databases.clickhouse import CHTableMeta, CHColumnMeta, ch

class Event(Base):
    __tablename__ = "events"
    id: Mapped[int] = mapped_column(primary_key=True)
    payload: Mapped[str] = mapped_column()
    event_time: Mapped[datetime] = mapped_column()

    class Meta(CHTableMeta):
        ch = ch_table(engine=merge_tree(), order_by="event_time")

        class payload(CHColumnMeta):
            ch = ch.field(codec="ZSTD(3)")

        class event_time(CHColumnMeta):
            ch = ch.field(default_expression="now()")

Generated DDL:

CREATE TABLE events (
    id Int64,
    payload String CODEC(ZSTD(3)),
    event_time DateTime DEFAULT now()
) ENGINE = MergeTree() ORDER BY event_time

Additional model examples

Column with codec and LowCardinality

class UserSession(Base):
    __tablename__ = "user_sessions"

    user_id: Mapped[int] = mapped_column(primary_key=True)
    browser: Mapped[str] = mapped_column()
    ip: Mapped[str] = mapped_column()
    duration: Mapped[int] = mapped_column()

    class Meta(CHTableMeta):
        ch = ch_table(engine=merge_tree(), order_by="user_id")

        class browser(CHColumnMeta):
            ch = ch.field(
                codec="ZSTD(3)",
                low_cardinality=True,
            )

        class ip(CHColumnMeta):
            ch = ch.field(
                codec="LZ4HC(9)",
                nullable=True,
            )

        class duration(CHColumnMeta):
            ch = ch.field(
                default_expression="0",
                ttl="now() - toIntervalDay(30)",
            )

Generated DDL:

CREATE TABLE user_sessions (
    user_id Int64,
    browser LowCardinality(String) CODEC(ZSTD(3)),
    ip Nullable(String) CODEC(LZ4HC(9)),
    duration Int64 DEFAULT 0 TTL now() - toIntervalDay(30)
) ENGINE = MergeTree() ORDER BY user_id

Column with alias and comment

class Metrics(Base):
    __tablename__ = "metrics"

    width: Mapped[float] = mapped_column()
    height: Mapped[float] = mapped_column()
    area: Mapped[float] = mapped_column()

    class Meta(CHTableMeta):
        ch = ch_table(engine=merge_tree(), order_by="width")

        class area(CHColumnMeta):
            ch = ch.field(
                alias="width * height",
                comment="Calculated area in pixels",
            )

REMOVE clause example

# Removing a codec from a column
class Meta(CHTableMeta):
    ch = ch_table(engine=merge_tree(), order_by="id")

    class payload(CHColumnMeta):
        ch = ch.field(codec=None)  # emits MODIFY COLUMN payload REMOVE CODEC

ch.field() options

Parameter Type SQL REMOVE form
codec str CODEC(ZSTD(3)) MODIFY COLUMN c REMOVE CODEC
default_expression str DEFAULT now() MODIFY COLUMN c REMOVE DEFAULT
materialized str MATERIALIZED expr MODIFY COLUMN c REMOVE MATERIALIZED
alias str ALIAS expr MODIFY COLUMN c REMOVE ALIAS
ephemeral str EPHEMERAL expr MODIFY COLUMN c REMOVE EPHEMERAL
ttl str TTL expr MODIFY COLUMN c REMOVE TTL
low_cardinality bool LowCardinality(String) Wrapped into type
nullable bool Nullable(String) Wrapped into type
comment str COMMENT ON COLUMN MODIFY COLUMN c REMOVE COMMENT

Setting a property to None (or omitting it) and then setting it to a value emits MODIFY COLUMN ... <property>. The reverse (removing a property) emits the REMOVE form. This is a write-only asymmetry in ClickHouse that dbwarden handles for you.

Type normalization

SQLAlchemy types are normalized to ClickHouse native types:

SQLAlchemy type ClickHouse type
Integer, BIGINT Int32, Int64
VARCHAR, String String
FLOAT(53), REAL Float64, Float32
NUMERIC(p,s) Decimal(p,s)
BOOLEAN Bool
ARRAY(Integer) Array(Int32)
Enum Enum8 / Enum16
UUID UUID
JSON JSON
DATETIME, DATETIME64 DateTime, DateTime64

What changes are allowed

Change Safety Notes
Add column INFO Standard ALTER TABLE ADD COLUMN
Drop column WARN Requires --force
Type change (compatible) INFO e.g. Int32Int64
Type change (incompatible) CRITICAL e.g. StringInt64, requires --force + recreate
Codec change WARN Relatively cheap
Default / Materialized / Alias change WARN
TTL change WARN
LowCardinality / Nullable toggle CRITICAL Requires --force
Comment change INFO
REMOVE any property INFO Emitted as MODIFY COLUMN c REMOVE ...

Rollback behavior

Column changes emit inverse operations. ADD COLUMN rolls back as DROP COLUMN. A MODIFY COLUMN rolls back as MODIFY COLUMN with the previous state.