From 82d40c7d39baa7c9e510eff4442422dafd9162a8 Mon Sep 17 00:00:00 2001 From: ruslandoga <67764432+ruslandoga@users.noreply.github.com> Date: Sun, 16 Apr 2023 18:28:51 +0700 Subject: [PATCH] more docs --- README.md | 165 +++++++++++++++++++++----------- lib/ecto/adapters/clickhouse.ex | 2 +- 2 files changed, 111 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 6ff80ab..9f17ae2 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/lib/ecto/adapters/clickhouse.ex b/lib/ecto/adapters/clickhouse.ex index 33bdc7c..7665a8e 100644 --- a/lib/ecto/adapters/clickhouse.ex +++ b/lib/ecto/adapters/clickhouse.ex @@ -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