#clickhouse #database #driver #tokio


a typed client for ClickHouse

16 releases (6 breaking)

0.7.0 May 31, 2021
0.6.2 Apr 12, 2021
0.6.0 Jan 20, 2021
0.5.1 Nov 22, 2020
0.1.0 May 17, 2017

#84 in Encoding

Download history 11/week @ 2021-02-23 5/week @ 2021-03-02 11/week @ 2021-03-09 8/week @ 2021-03-16 22/week @ 2021-03-23 15/week @ 2021-03-30 43/week @ 2021-04-06 37/week @ 2021-04-13 130/week @ 2021-04-20 5/week @ 2021-04-27 8/week @ 2021-05-04 54/week @ 2021-05-11 35/week @ 2021-05-18 106/week @ 2021-05-25 72/week @ 2021-06-01 54/week @ 2021-06-08

150 downloads per month
Used in 2 crates


2.5K SLoC


Build status Crate info Documentation

A typed client for ClickHouse.


  • Uses serde for encoding/decoding rows.
  • Uses RowBinary encoding.
  • Provides API for selecting.
  • Provides API for infinite transactional (see below) inserting.
  • Provides API for watching live views.
  • Decompression (lz4, gzip, zlib and brotli). lz4 by default.


To use the crate, add this to your Cargo.toml:

clickhouse = "0.7"

clickhouse = { version = "0.7", features = ["test-util"] }


See more examples.

Create Client

use clickhouse::Client;

let client = Client::default()
  • Reuse created clients or clone them in order to reuse a connection pool.

Select rows

use serde::Deserialize;
use clickhouse::Row;

#[derive(Row, Deserialize)]
struct MyRow<'a> {
    no: u32,
    name: &'a str,

let mut cursor = client
    .query("SELECT ?fields FROM some WHERE no BETWEEN ? AND ?")

while let Some(row) = cursor.next().await? { .. }
  • Placeholder ?fields is replaced with no, name (fields of Row).
  • Placeholder ? is replaced with values in following bind() calls.
  • Convenient fetch_one::<Row>() and fetch_all::<Row>() can be used to get a first row or all rows correspondingly.
  • sql::Identifier can be used to bind table names.
  • soa-derive is useful for Nested types.

Insert a batch

let mut insert = client.insert("some")?;
insert.write(&Row { no: 0, name: "foo" }).await?;
insert.write(&Row { no: 1, name: "bar" }).await?;
  • If end() isn't called the insertion will be aborted.
  • Rows are being sent progressively to spread network load.
  • ClickHouse inserts batches atomically only if all rows fit in the same partition and their number is less max_insert_block_size.
  • ch2rs is useful to generate a row type from ClickHouse.

Infinite inserting

let mut inserter = client.inserter("some")?
    .with_max_entries(150_000) // `250_000` by default
    .with_max_duration(Duration::from_secs(15)); // `10s` by default

inserter.write(&Row { no: 0, name: "foo" }).await?;
inserter.write(&Row { no: 1, name: "bar" }).await?;
let stats = inserter.commit().await?;
if stats.entries > 0 {
    println!("{} entries ({} transactions) have been inserted",
        stats.entries, stats.transactions);
  • Inserter ends an active insert in commit() if thresholds (max_entries, max_duration) are reached.
  • The interval between ending active inserts is biased (±10% of max_duration) to avoid load spikes by parallel inserters.
  • All rows between commit() calls are inserted in the same INSERT statement.
  • Do not forget to flush if you want yo terminate inserting:

Perform DDL

client.query("DROP TABLE IF EXISTS some").execute().await?;

Live views

let mut cursor = client
    .watch("SELECT max(no), argMax(name, no) FROM some")

let (version, row) = cursor.next().await?.unwrap();
println!("live view updated: version={}, row={:?}", version, row);

// Use `only_events()` to iterate over versions only.
let mut cursor = client.watch("some_live_view").limit(20).only_events().fetch()?;
println!("live view updated: version={:?}", cursor.next().await?);
  • This code uses or creates if not exists a temporary live view named lv_{sha1(query)} to reuse the same live view by parallel watchers.
  • You can specify a name instead of a query.


The crate provides utils for mocking CH server, both for SELECTs and INSERTs.

The functionality can be enabled with the test-util feature. Use it only in dev-dependencies.

See the example.


~207K SLoC