AshClickhouse generates ClickHouse DDL directly from your Ash resources. There
is no separate migration DSL — the table schema is derived from the resource's
attributes and clickhouse DSL options.
Tasks
Two Mix tasks are provided:
mix ash_clickhouse.setup # CREATE DATABASE IF NOT EXISTS for each repo
mix ash_clickhouse.migrate # CREATE TABLE (+ ALTER) for each resource
mix ash_clickhouse.setup iterates over all AshClickhouse.Repo modules and
calls create_database/0.
mix ash_clickhouse.migrate iterates over all resources that export
__ash_clickhouse__/1, filters by migrate?/1 and matching repo/1, then:
- Runs
Migration.generate_resource_cql/1(CREATE TABLE IF NOT EXISTS) - Runs
Migration.alter_table_cql/2(schema evolution for existing tables) - Runs
Migration.alter_indexes_cql/2(adds missing data-skipping indexes)
Resources with migrate false are skipped.
Generated CREATE TABLE
AshClickhouse.Migration.create_table_cql/1 produces a statement like:
CREATE TABLE IF NOT EXISTS "my_app_dev"."users" (
"id" UUID,
"name" String,
"email" String,
"age" Int64,
"inserted_at" DateTime64(6)
)
ENGINE = MergeTree()
ORDER BY (id)The generated clauses are:
ENGINE = <engine>(defaultMergeTree())PARTITION BY <partition_by>(if set)ORDER BY (<order_by>)(required for most engines)PRIMARY KEY (<primary_key>)(if set)SETTINGS <settings>(if set)INDEX <name> (<expression>) TYPE <type> GRANULARITY <n>(one perindexin theclickhouseblock)
The database is qualified only when a database is configured on the resource
or repo.
Schema evolution
Because ClickHouse does not support transactional migrations the way
relational databases do, alter_table_cql/2 emits ALTER TABLE statements to
add new columns when the resource definition changes. Run mix ash_clickhouse.migrate
again after changing attributes to apply the diff.
Data-skipping indexes
ClickHouse has no B-tree indexes; it uses data-skipping indexes declared in
the table DDL. Declare them in the clickhouse block (the index macro may be
repeated, and granularity defaults to 1):
clickhouse do
table "events"
repo MyApp.Repo
order_by "id"
index name: :idx_user_id, expression: "user_id", type: "bloom_filter"
index name: :idx_created_at, expression: "created_at", type: "minmax", granularity: 4
endmix ash_clickhouse.migrate emits the indexes in the CREATE TABLE statement
for new tables, and issues ALTER TABLE ... ADD INDEX IF NOT EXISTS for
existing tables that lack them. Index type is validated against a whitelist
(minmax, set, bloom_filter, ngrambf_v1, tokenbf_v1) at compile time,
so a typo fails at compile rather than at migrate.
Indexes are additive only. Changing an existing index's type or
expression is not auto-applied — ADD INDEX IF NOT EXISTS no-ops on a name
collision. Instead, the migrate task prints a warning (with the exact
DROP INDEX + ADD INDEX SQL to run) when an existing index's stored
definition differs from the DSL. To change a definition, manually run:
ALTER TABLE my_db.events DROP INDEX `idx_user_id`;
ALTER TABLE my_db.events ADD INDEX `idx_user_id` (user_id) TYPE bloom_filter GRANULARITY 1;The expression comparison is best-effort (ClickHouse normalizes stored
expressions), so treat a type mismatch as authoritative and double-check
expression mismatches by hand before acting.
Type mapping
Column types are derived from Ash attribute types. See Types for the complete table.
Tips
- Always set
order_by— most ClickHouse engines require it. - Use
partition_by(e.g.toYYYYMM(inserted_at)) for large time-series tables to improve pruning. - Keep
migrate falseon resources you manage outside AshClickhouse.