Official pure Rust typed client for ClickHouse DB.
- Uses 
serdefor encoding/decoding rows. - Supports 
serdeattributes:skip_serializing,skip_deserializing,rename. - Uses 
RowBinaryencoding over HTTP transport.- There are plans to switch to 
Nativeover TCP. 
 - There are plans to switch to 
 - Supports TLS (see 
native-tlsandrustls-tlsfeatures below). - Supports compression and decompression (LZ4 and LZ4HC).
 - Provides API for selecting.
 - Provides API for inserting.
 - Provides API for infinite transactional (see below) inserting.
 - Provides API for watching live views.
 - Provides mocks for unit testing.
 
Note: ch2rs is useful to generate a row type from ClickHouse.
To use the crate, add this to your Cargo.toml:
[dependencies]
clickhouse = "0.13.1"
[dev-dependencies]
clickhouse = { version = "0.13.1", features = ["test-util"] }CH server older than v22.6 (2022-06-16) handles RowBinary incorrectly in some rare cases. Use 0.11 and enable wa-37420 feature to solve this problem. Don't use it for newer versions.
use clickhouse::Client;
let client = Client::default()
    .with_url("http://localhost:8123")
    .with_user("name")
    .with_password("123")
    .with_database("test");- Reuse created clients or clone them in order to reuse a connection pool.
 
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 ?")
    .bind(500)
    .bind(504)
    .fetch::<MyRow<'_>>()?;
while let Some(row) = cursor.next().await? { .. }- Placeholder 
?fieldsis replaced withno, name(fields ofRow). - Placeholder 
?is replaced with values in followingbind()calls. - Convenient 
fetch_one::<Row>()andfetch_all::<Row>()can be used to get a first row or all rows correspondingly. sql::Identifiercan be used to bind table names.
Note that cursors can return an error even after producing some rows. To avoid this, use client.with_option("wait_end_of_query", "1") in order to enable buffering on the server-side. More details. The buffer_size option can be useful too.
use serde::Serialize;
use clickhouse::Row;
#[derive(Row, Serialize)]
struct MyRow {
    no: u32,
    name: String,
}
let mut insert = client.insert("some")?;
insert.write(&MyRow { no: 0, name: "foo".into() }).await?;
insert.write(&MyRow { no: 1, name: "bar".into() }).await?;
insert.end().await?;- If 
end()isn't called, theINSERTis 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. 
Requires the inserter feature.
let mut inserter = client.inserter("some")?
    .with_timeouts(Some(Duration::from_secs(5)), Some(Duration::from_secs(20)))
    .with_max_bytes(50_000_000)
    .with_max_rows(750_000)
    .with_period(Some(Duration::from_secs(15)));
inserter.write(&MyRow { no: 0, name: "foo".into() })?;
inserter.write(&MyRow { no: 1, name: "bar".into() })?;
let stats = inserter.commit().await?;
if stats.rows > 0 {
    println!(
        "{} bytes, {} rows, {} transactions have been inserted",
        stats.bytes, stats.rows, stats.transactions,
    );
}Please, read examples to understand how to use it properly in different real-world cases.
Inserterends an active insert incommit()if thresholds (max_bytes,max_rows,period) are reached.- The interval between ending active 
INSERTs can be biased by usingwith_period_biasto avoid load spikes by parallel inserters. Inserter::time_left()can be used to detect when the current period ends. CallInserter::commit()again to check limits if your stream emits items rarely.- Time thresholds implemented by using quanta crate to speed the inserter up. Not used if 
test-utilis enabled (thus, time can be managed bytokio::time::advance()in custom tests). - All rows between 
commit()calls are inserted in the sameINSERTstatement. - Do not forget to flush if you want to terminate inserting:
 
inserter.end().await?;Requires the watch feature.
let mut cursor = client
    .watch("SELECT max(no), argMax(name, no) FROM some")
    .fetch::<Row<'_>>()?;
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?);- Use carefully.
 - 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.
 - This API uses 
JSONEachRowWithProgressunder the hood because of the issue. - Only struct rows can be used. Avoid 
fetch::<u64>()and other without specified names. 
See examples.
lz4(enabled by default) — enablesCompression::Lz4. If enabled,Compression::Lz4is used by default for all queries except forWATCH.inserter— enablesclient.inserter().test-util— adds mocks. See the example. Use it only indev-dependencies.watch— enablesclient.watchfunctionality. See the corresponding section for details.uuid— addsserde::uuidto work with uuid crate.time— addsserde::timeto work with time crate.chrono— addsserde::chronoto work with chrono crate.
By default, TLS is disabled and one or more following features must be enabled to use HTTPS urls:
native-tls— uses native-tls, utilizing dynamic linking (e.g. against OpenSSL).rustls-tls— enablesrustls-tls-aws-lcandrustls-tls-webpki-rootsfeatures.rustls-tls-aws-lc— uses rustls with theaws-lccryptography implementation.rustls-tls-ring— uses rustls with theringcryptography implementation.rustls-tls-webpki-roots— uses rustls with certificates provided by the webpki-roots crate.rustls-tls-native-roots— uses rustls with certificates provided by the rustls-native-certs crate.
If multiple features are enabled, the following priority is applied:
native-tls>rustls-tls-aws-lc>rustls-tls-ringrustls-tls-native-roots>rustls-tls-webpki-roots
How to choose between all these features? Here are some considerations:
- A good starting point is 
rustls-tls, e.g. if you use ClickHouse Cloud. - To be more environment-agnostic, prefer 
rustls-tlsovernative-tls. - Enable 
rustls-tls-native-rootsornative-tlsif you want to use self-signed certificates. 
- 
(U)Int(8|16|32|64|128)maps to/from corresponding(u|i)(8|16|32|64|128)types or newtypes around them. - 
(U)Int256aren't supported directly, but there is a workaround for it. - 
Float(32|64)maps to/from correspondingf(32|64)or newtypes around them. - 
Decimal(32|64|128)maps to/from correspondingi(32|64|128)or newtypes around them. It's more convenient to use fixnum or another implementation of signed fixed-point numbers. - 
Booleanmaps to/fromboolor newtypes around it. - 
Stringmaps to/from any string or bytes types, e.g.&str,&[u8],String,Vec<u8>orSmartString. Newtypes are also supported. To store bytes, consider using serde_bytes, because it's more efficient.Example
#[derive(Row, Debug, Serialize, Deserialize)] struct MyRow<'a> { str: &'a str, string: String, #[serde(with = "serde_bytes")] bytes: Vec<u8>, #[serde(with = "serde_bytes")] byte_slice: &'a [u8], }
 - 
FixedString(N)is supported as an array of bytes, e.g.[u8; N].Example
#[derive(Row, Debug, Serialize, Deserialize)] struct MyRow { fixed_str: [u8; 16], // FixedString(16) }
 - 
Enum(8|16)are supported using serde_repr.Example
use serde_repr::{Deserialize_repr, Serialize_repr}; #[derive(Row, Serialize, Deserialize)] struct MyRow { level: Level, } #[derive(Debug, Serialize_repr, Deserialize_repr)] #[repr(u8)] enum Level { Debug = 1, Info = 2, Warn = 3, Error = 4, }
 - 
UUIDmaps to/fromuuid::Uuidby usingserde::uuid. Requires theuuidfeature.Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { #[serde(with = "clickhouse::serde::uuid")] uuid: uuid::Uuid, }
 - 
IPv6maps to/fromstd::net::Ipv6Addr. - 
IPv4maps to/fromstd::net::Ipv4Addrby usingserde::ipv4.Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { #[serde(with = "clickhouse::serde::ipv4")] ipv4: std::net::Ipv4Addr, }
 - 
Datemaps to/fromu16or a newtype around it and represents a number of days elapsed since1970-01-01. The following external types are supported:time::Dateis supported by usingserde::time::date, requiring thetimefeature.chrono::NaiveDateis supported by usingserde::chrono::date, requiring thechronofeature.
Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { days: u16, #[serde(with = "clickhouse::serde::time::date")] date: Date, // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::date")] date_chrono: NaiveDate, }
 - 
Date32maps to/fromi32or a newtype around it and represents a number of days elapsed since1970-01-01. The following external types are supported:time::Dateis supported by usingserde::time::date32, requiring thetimefeature.chrono::NaiveDateis supported by usingserde::chrono::date32, requiring thechronofeature.
Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { days: i32, #[serde(with = "clickhouse::serde::time::date32")] date: Date, // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::date32")] date_chrono: NaiveDate, }
 - 
DateTimemaps to/fromu32or a newtype around it and represents a number of seconds elapsed since UNIX epoch. The following external types are supported:time::OffsetDateTimeis supported by usingserde::time::datetime, requiring thetimefeature.chrono::DateTime<Utc>is supported by usingserde::chrono::datetime, requiring thechronofeature.
Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { ts: u32, #[serde(with = "clickhouse::serde::time::datetime")] dt: OffsetDateTime, // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::datetime")] dt_chrono: DateTime<Utc>, }
 - 
DateTime64(_)maps to/fromi64or a newtype around it and represents a time elapsed since UNIX epoch. The following external types are supported:time::OffsetDateTimeis supported by usingserde::time::datetime64::*, requiring thetimefeature.chrono::DateTime<Utc>is supported by usingserde::chrono::datetime64::*, requiring thechronofeature.
Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { ts: i64, // elapsed s/us/ms/ns depending on `DateTime64(X)` #[serde(with = "clickhouse::serde::time::datetime64::secs")] dt64s: OffsetDateTime, // `DateTime64(0)` #[serde(with = "clickhouse::serde::time::datetime64::millis")] dt64ms: OffsetDateTime, // `DateTime64(3)` #[serde(with = "clickhouse::serde::time::datetime64::micros")] dt64us: OffsetDateTime, // `DateTime64(6)` #[serde(with = "clickhouse::serde::time::datetime64::nanos")] dt64ns: OffsetDateTime, // `DateTime64(9)` // if you prefer using chrono: #[serde(with = "clickhouse::serde::chrono::datetime64::secs")] dt64s_chrono: DateTime<Utc>, // `DateTime64(0)` #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] dt64ms_chrono: DateTime<Utc>, // `DateTime64(3)` #[serde(with = "clickhouse::serde::chrono::datetime64::micros")] dt64us_chrono: DateTime<Utc>, // `DateTime64(6)` #[serde(with = "clickhouse::serde::chrono::datetime64::nanos")] dt64ns_chrono: DateTime<Utc>, // `DateTime64(9)` }
 - 
Tuple(A, B, ...)maps to/from(A, B, ...)or a newtype around it. - 
Array(_)maps to/from any slice, e.g.Vec<_>,&[_]. Newtypes are also supported. - 
Map(K, V)behaves likeArray((K, V)). - 
LowCardinality(_)is supported seamlessly. - 
Nullable(_)maps to/fromOption<_>. Forclickhouse::serde::*helpers add::option.Example
#[derive(Row, Serialize, Deserialize)] struct MyRow { #[serde(with = "clickhouse::serde::ipv4::option")] ipv4_opt: Option<Ipv4Addr>, }
 - 
Nestedis supported by providing multiple arrays with renaming.Example
// CREATE TABLE test(items Nested(name String, count UInt32)) #[derive(Row, Serialize, Deserialize)] struct MyRow { #[serde(rename = "items.name")] items_name: Vec<String>, #[serde(rename = "items.count")] items_count: Vec<u32>, }
 - 
Geotypes are supported.Pointbehaves like a tuple(f64, f64), and the rest of the types are just slices of points.Example
type Point = (f64, f64); type Ring = Vec<Point>; type Polygon = Vec<Ring>; type MultiPolygon = Vec<Polygon>; type LineString = Vec<Point>; type MultiLineString = Vec<LineString>; #[derive(Row, Serialize, Deserialize)] struct MyRow { point: Point, ring: Ring, polygon: Polygon, multi_polygon: MultiPolygon, line_string: LineString, multi_line_string: MultiLineString, }
 - 
Variantdata type is supported as a Rust enum. As the inner Variant types are always sorted alphabetically, Rust enum variants should be defined in the exactly same order as it is in the data type; their names are irrelevant, only the order of the types matters. This following example has a column defined asVariant(Array(UInt16), Bool, Date, String, UInt32):Example
#[derive(Serialize, Deserialize)] enum MyRowVariant { Array(Vec<i16>), Boolean(bool), #[serde(with = "clickhouse::serde::time::date")] Date(time::Date), String(String), UInt32(u32), } #[derive(Row, Serialize, Deserialize)] struct MyRow { id: u64, var: MyRowVariant, }
 - 
New
JSONdata type is currently supported as a string when using ClickHouse 24.10+. See this example for more details. - 
Dynamicdata type is not supported for now. 
See also the additional examples:
The crate provides utils for mocking CH server and testing DDL, SELECT, INSERT and WATCH queries.
The functionality can be enabled with the test-util feature. Use it only in dev-dependencies.
See the example.