ChDriver (ch_driver v0.1.1)

Copy Markdown

Native ClickHouse TCP-protocol driver, exposed as a DBConnection pool.

Usage

{:ok, pool} = ChDriver.start_link(hostname: "localhost", port: 9000)
{:ok, %ChDriver.Result{columns: columns, rows: rows}} =
  ChDriver.query(pool, "SELECT number FROM system.numbers LIMIT 5")

start_link/1, query/2,3,4, query!/2,3,4, and stream/2,3,4 are the whole public surface. Everything else in this library is internal wiring (used directly by Ecto.Adapters.ClickHouse, but not meant to be called from application code).

start_link/1 accepts all of ChDriver.Connection.connect/1's options (:hostname, :port, :database, :username, :password, :connect_timeout, :recv_timeout, :compression) plus the usual DBConnection.start_link/2 pool options (:pool_size, :name, etc.), and forwards the connection options to every pooled connection.

:compression (:none by default, or :lz4) turns on wire compression for query result blocks. Set it once at pool start, or override it per call via query/4's opts.

Summary

Functions

Runs statement against conn and returns its result.

Same as query/4, but returns the %ChDriver.Result{} directly on success and raises the underlying error instead of returning {:error, reason}.

Starts a pool of ClickHouse connections.

Streams the rows of statement one wire-protocol block at a time, instead of loading the whole result into memory the way query/2,3,4 does.

Functions

query(conn, statement, params \\ [], opts \\ [])

@spec query(DBConnection.conn(), binary(), list(), keyword()) ::
  {:ok, ChDriver.Result.t()} | {:error, Exception.t()}

Runs statement against conn and returns its result.

statement is a raw SQL string. It can contain plain ? positional placeholders, or ClickHouse's own {name:Type} named placeholders written directly.

params is the list of Elixir values to bind, in the same order as the ?s (or {name:Type}s) in statement:

ChDriver.query(pool, "SELECT * FROM events WHERE user_id = ?", [42])

conn is a pool started by start_link/1, or any DBConnection-compatible connection reference.

Returns {:ok, %ChDriver.Result{}} or {:error, reason}.

query!(conn, statement, params \\ [], opts \\ [])

@spec query!(DBConnection.conn(), binary(), list(), keyword()) :: ChDriver.Result.t()

Same as query/4, but returns the %ChDriver.Result{} directly on success and raises the underlying error instead of returning {:error, reason}.

start_link(opts \\ [])

@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}

Starts a pool of ClickHouse connections.

Returns {:ok, pid} (the pool to pass as conn to query/2,3,4 or stream/2,3,4) or {:error, reason}.

stream(conn, statement, params \\ [], opts \\ [])

@spec stream(DBConnection.conn(), binary(), list(), keyword()) :: ChDriver.Stream.t()

Streams the rows of statement one wire-protocol block at a time, instead of loading the whole result into memory the way query/2,3,4 does.

Returns a %ChDriver.Stream{}, an Enumerable whose elements are %ChDriver.Result{} structs (one per block), honoring opts[:decode_mapper] the same way query/2,3,4 does.

conn must already be a checked-out connection, so stream/2,3,4 has to be called from inside DBConnection.run/3 or DBConnection.transaction/3, not directly against a pool:

{:ok, result} =
  DBConnection.run(pool, fn conn ->
    conn
    |> ChDriver.stream("SELECT number FROM system.numbers LIMIT 200000")
    |> Enum.take(5)
  end)

Each block is only read off the socket when the consumer asks for the next one, so Enum.take/2 on a large query genuinely avoids buffering rows you never asked for.