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, distinct
  • bulk_create (batch INSERT)
  • update_query / destroy_query (via ALTER 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 transactions
  • keyset — ClickHouse has no token-based keyset pagination (use offset)
  • upsert — ClickHouse has no ON CONFLICT (use create + update_query)
  • join — JOINs are not yet implemented
  • combine (UNION/INTERSECT) — not yet implemented
  • filter_relationship / exists (unrelated) / aggregate_relationship — not yet implemented

Installation

def deps do
  [
    {:ash_clickhouse, "~> 0.1.0"}
  ]
end

Quick 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
end

4. 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)

OptionDescription
tableTable name (overrides default)
repoThe AshClickhouse.Repo module
databaseDatabase to use (overrides repo default)
engineClickHouse table engine (default "MergeTree()")
order_byORDER BY expression for the engine
partition_byPARTITION BY expression
primary_keyExplicit primary key columns
settingsEngine settings string
base_filterFilter applied to all queries
default_contextContext merged into every query/changeset
migrateWhether to include in migrations (default true)
indexDeclares 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
end

Indexes 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

OptionDefault
url"http://localhost:8123"
username"default"
password""
databasenil
timeout30_000

License

Apache-2.0. See LICENSE.