Note: This library is under active development, unstable and the API may change.
An Ash Framework data layer for ClickHouse. It implements the
Ash.DataLayer behaviour using the clickhouse client.
Features
ClickHouse is a full SQL columnar store, so most Ash query features map directly:
- CRUD (
create,read,update,destroy) filter,sort,limit,offset,select,distinctbulk_create(batchINSERT)update_query/destroy_query(viaALTER TABLE ... UPDATE/DELETE)- Native aggregates (
count,sum,avg,min,max) multitenancy(database- or attribute-based)calculate,composite_primary_key,nested_expressions,boolean_filter
Not supported
transact/lock— ClickHouse has no multi-statement transactionskeyset— ClickHouse has no token-based keyset pagination (useoffset)upsert— ClickHouse has noON CONFLICT(usecreate+update_query)join— JOINs are not yet implementedcombine(UNION/INTERSECT) — not yet implementedfilter_relationship/exists(unrelated) /aggregate_relationship— not yet implemented
Installation
def deps do
[
{:ash_clickhouse, "~> 0.1.0"}
]
endQuick start
1. Configure a Repo
defmodule MyApp.Repo do
use AshClickhouse.Repo, otp_app: :my_app
end# config/config.exs
config :my_app, MyApp.Repo,
url: "http://localhost:8123",
database: "my_app_dev"2. Add to your supervision tree
children = [MyApp.Repo, ...]3. Define a resource
defmodule MyApp.User do
use Ash.Resource,
data_layer: AshClickhouse.DataLayer,
domain: MyApp.Domain
import AshClickhouse.DataLayer.Dsl.Macros
clickhouse do
table "users"
repo MyApp.Repo
engine "MergeTree()"
order_by "id"
end
attributes do
uuid_primary_key :id
attribute :name, :string
attribute :email, :string
end
actions do
defaults [:create, :read, :update, :destroy]
end
end4. Create the database and tables
mix ash_clickhouse.setup
mix ash_clickhouse.migrate
5. Use it
{:ok, user} = Ash.create(MyApp.User, %{name: "John", email: "john@example.com"})
users = Ash.read!(MyApp.User)Configuration
Resource (clickhouse DSL)
| Option | Description |
|---|---|
table | Table name (overrides default) |
repo | The AshClickhouse.Repo module |
database | Database to use (overrides repo default) |
engine | ClickHouse table engine (default "MergeTree()") |
order_by | ORDER BY expression for the engine |
partition_by | PARTITION BY expression |
primary_key | Explicit primary key columns |
settings | Engine settings string |
base_filter | Filter applied to all queries |
default_context | Context merged into every query/changeset |
migrate | Whether to include in migrations (default true) |
index | Declares a data-skipping index (repeatable) |
Data-skipping indexes
ClickHouse has no B-tree indexes; it uses data-skipping indexes (minmax,
set, bloom_filter, ngrambf_v1, tokenbf_v1) declared in the table DDL.
Declare them inside 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
endIndexes are additive only. mix ash_clickhouse.migrate emits them in the
CREATE TABLE statement for new tables, and issues ALTER TABLE ... ADD INDEX IF NOT EXISTS for existing tables that lack them. Changing an existing index's
type/expression is not auto-applied — ADD INDEX IF NOT EXISTS no-ops on
a name collision. The migrate task instead 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 ALTER TABLE ... DROP INDEX and re-run the migration. (A typo in type is rejected at compile
time.)
Repo
| Option | Default |
|---|---|
url | "http://localhost:8123" |
username | "default" |
password | "" |
database | nil |
timeout | 30_000 |
License
Apache-2.0. See LICENSE.