Skip to content

Commit

Permalink
more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Apr 17, 2023
1 parent 70884e4 commit 82d40c7
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 56 deletions.
165 changes: 110 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,119 @@
Ecto Adapter for ClickHouse using [`:ch`](https://github.com/plausible/ch)
# Ecto ClickHouse Adapter

[![Hex Package](https://img.shields.io/hexpm/v/chto.svg)](https://hex.pm/packages/chto)
[![Hex Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/chto)

Uses [Ch](https://github.com/plausible/ch) as driver.

## Installation

```elixir
defp deps do
[
{:chto, github: "plausible/chto"}
]
end
```

## Usage

In your `config/config.exs`

```elixir
config :my_app, ecto_repos: [MyApp.Repo]
config :my_app, MyApp.Repo, url: "http://username:password@localhost:8123/database"
```

In your application code

```elixir
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.ClickHouse
end
```

## Caveats

#### Ecto schemas

For automatic RowBinary encoding some schema fields need to use custom types:

```elixir
defmodule MyApp.Example do
use Ecto.Schema

@primary_key false
schema "example" do
field :numeric_types_need_size, Ch.Types.UInt32
field :no_custom_type_for_strings, :string
field :datetime, :naive_datetime
field :maybe_name, Ch.Types.Nullable, type: :string
field :country_code, Ch.Types.FixedString, size: 2
field :price, Ch.Types.Decimal32, scale: 2
end
end

MyApp.Repo.insert_all(MyApp.Example, rows)
```

#### Schemaless inserts

For schemaless inserts `:types` is required

```elixir
## speedrun

iex> Mix.install([{:chto, github: "plausible/chto"}])

iex> defmodule Repo do
use Ecto.Repo, adapter: Ecto.Adapters.ClickHouse, otp_app: :example
end

iex> import Ecto.Query
iex> Repo.start_link()

iex> Repo.query!("create table example(a UInt32, b String, c DateTime) engine=MergeTree order by tuple()")

iex> defmodule Example do
use Ecto.Schema

@primary_key false
schema "example" do
field :a, Ch.Types.UInt32
field :b, :string
field :c, :naive_datetime
end
end

iex> Repo.insert_all("example", [%{a: 1, b: "2"}, %{a: 3, c: nil}], types: [a: :u32, b: :string, c: :datetime])
{2, nil}

iex> Repo.insert_all(Example, [%{a: 5, b: "5"}, %{a: 6}])
{2, nil}

iex> Example |> order_by(desc: :a) |> limit(2) |> Repo.all()
[
%Example{
a: 6,
b: "",
c: ~N[1970-01-01 00:00:00]
}
%Example{
a: 5,
b: "5",
c: ~N[1970-01-01 00:00:00]
}
types = [
numeric_types_need_size: :u32,
no_custom_type_for_strings: :string,
datetime: :datetime,
maybe_name: {:nullable, :string},
country_code: {:string, _size = 2},
price: {:decimal, _size = 32, _scale = 2}
]

iex> Repo.insert_all(Example, select(Example, [e], %{a: e.a, b: e.b}))
{4, nil}
MyApp.Repo.insert_all("example", rows, types: types)
```

iex> Repo.update_all(Example, set: [a: 2])
# ** (Ecto.QueryError) ClickHouse does not support UPDATE statements -- use ALTER TABLE instead in query:
# from e0 in Dev.Example,
# update: [set: [a: ^...]]
#### Settings

# count is 0 since clickhouse doesn't (seem to) respond with how many rows been deleted
iex> Repo.delete_all(Example, settings: [allow_experimental_lightweight_delete: 1, mutations_sync: 1])
{0, nil}
`:settings` option can be used to enable [asynchronous inserts,](https://clickhouse.com/docs/en/optimize/asynchronous-inserts) lightweght [deletes,](https://clickhouse.com/docs/en/guides/developer/lightweght-delete) and [more](https://clickhouse.com/docs/en/operations/settings/settings)

iex> Repo.aggregate(Example, :count)
0
```elixir
MyApp.Repo.insert_all(MyApp.Example, rows, settings: [async_insert: 1])
MyApp.Repo.delete_all("example", settings: [allow_experimental_lightweight_delete: 1])
```

#### [ARRAY JOIN](https://clickhouse.com/docs/en/sql-reference/statements/select/array-join)

`:inner_lateral` and `:left_lateral` join types are used for `ARRAY JOIN` and `LEFT ARRAY JOIN` until Ecto adds `:array_join` types.

iex> Repo.query!("drop table example")
`ARRAY JOIN` example:

```elixir
"arrays_test"
|> join(:inner_lateral, [a], r in "arr", on: true)
|> select([a, r], {a.s, r.arr})
```

```sql
SELECT a0."s", a1."arr"
FROM "arrays_test" AS a0
ARRAY JOIN "arr" AS a1
```

#### NULL

`DEFAULT` expressions on columns are ignored when inserting RowBinary.

[See Ch for more details and an example.](https://github.com/plausible/ch#null-in-rowbinary)

#### UTF-8

Both `:binary` and `:string` schema fields are decoded as UTF-8 since Ecto [doesn't call adapter's loaders for base types](https://github.com/elixir-ecto/ecto/blob/b5682bbd2123d32760af664cc3f91c5d8174ef74/lib/ecto/type.ex#L891-L897) like `:binary` and `:string`.

[See Ch for more details and an example.](https://github.com/plausible/ch#utf-8-in-rowbinary)

## Benchmarks

[See Ch for benchmarks.](https://github.com/plausible/ch#benchmarks)
2 changes: 1 addition & 1 deletion lib/ecto/adapters/clickhouse.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Ecto.Adapters.ClickHouse do
@moduledoc "Ecto adapter for a minimal HTTP ClickHouse client"
@moduledoc "Ecto adapter for HTTP ClickHouse client"

@behaviour Ecto.Adapter
@behaviour Ecto.Adapter.Migration
Expand Down

0 comments on commit 82d40c7

Please sign in to comment.