A typed client for ClickHouse.
serde for encoding/decoding rows.RowBinary encoding.To use the crate, add this to your Cargo.toml:
```toml
[dependencies]
clickhouse = "0.11.4"
[dev-dependencies] clickhouse = { version = "0.11.4", features = ["test-util"] } ```
CH server older than v22.6 (2022-06-16) handles RowBinary incorrectly in some rare cases. Enable wa-37420 feature to solve this problem. Don't use it for newer versions.
```rust,ignore use clickhouse::Client;
let client = Client::default() .withurl("http://localhost:8123") .withuser("name") .withpassword("123") .withdatabase("test"); ```
```rust,ignore use serde::Deserialize; use clickhouse::Row;
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::
while let Some(row) = cursor.next().await? { .. } ```
?fields is replaced with no, name (fields of Row).? is replaced with values in following bind() calls.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.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.
```rust,ignore use serde::Serialize; use clickhouse::Row;
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?; ```
end() isn't called, the INSERT is aborted.max_insert_block_size.
```rust,ignore let mut inserter = client.inserter("some")? .withtimeouts(Some(Duration::fromsecs(5)), Some(Duration::fromsecs(20))) .withmaxentries(750000) .withperiod(Some(Duration::fromsecs(15)));
inserter.write(&MyRow { no: 0, name: "foo".into() }).await?; inserter.write(&MyRow { no: 1, name: "bar".into() }).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, period) are reached.INSERTs can be biased by using with_period_bias to avoid load spikes by parallel inserters.commit() calls are inserted in the same INSERT statement.rust,ignore
inserter.end().await?;
rust,ignore
client.query("DROP TABLE IF EXISTS some").execute().await?;
Requires the watch feature.
```rust,ignore
let mut cursor = client
.watch("SELECT max(no), argMax(name, no) FROM some")
.fetch::
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("someliveview").limit(20).only_events().fetch()?;
println!("live view updated: version={:?}", cursor.next().await?);
```
lv_{sha1(query)} to reuse the same live view by parallel watchers.JSONEachRowWithProgress under the hood because of the issue.fetch::<u64>() and other without specified names.See examples.
lz4 (enabled by default) — enables Compression::Lz4 and Compression::Lz4Hc(_) variants. If enabled, Compression::Lz4 is used by default for all queries except for WATCH.tls (enabled by default) — supports urls with the HTTPS schema.test-util — adds mocks. See the example. Use it only in dev-dependencies.watch — enables client.watch functionality. See the corresponding section for details.uuid — adds serde::uuid to work with uuid crate.time — adds serde::time to work with time crate.wa-37420 — implements a workaround for CH versions prior to v22.6. See the corresponding section for details.(U)Int(8|16|32|64|128) maps to/from corresponding (u|i)(8|16|32|64|128) types or newtypes around them.(U)Int256 aren't supported directly, but there is a workaround for it.Float(32|64) maps to/from corresponding f(32|64) or newtypes around them.Decimal(32|64|128) maps to/from corresponding i(32|64|128) or newtypes around them. It's more convenient to use fixnum or another implementation of signed fixed-point numbers.Boolean maps to/from bool or newtypes around it.String maps to/from any string or bytes types, e.g. &str, &[u8], String, Vec<u8> or SmartString. Newtypes are also supported. To store bytes, consider using serde_bytes, because it's more efficient.
Example
```rust,ignore
struct MyRow<'a> {
str: &'a str,
string: String,
#[serde(with = "serdebytes")]
bytes: Vec
FixedString(_) isn't supported yet.Enum(8|16) are supported using serde_repr.
Example
```rust,ignore use serderepr::{Deserializerepr, Serialize_repr};
struct MyRow { level: Level, }
enum Level { Debug = 1, Info = 2, Warn = 3, Error = 4, } ```
UUID maps to/from uuid::Uuid by using serde::uuid. Requires the uuid feature.
Example
```rust,ignore
struct MyRow { #[serde(with = "clickhouse::serde::uuid")] uuid: uuid::Uuid, } ```
IPv6 maps to/from std::net::Ipv6Addr.IPv4 maps to/from std::net::Ipv4Addr by using serde::ipv4.
Example
```rust,ignore
struct MyRow { #[serde(with = "clickhouse::serde::ipv4")] ipv4: std::net::Ipv4Addr, } ```
Date maps to/from u16 or a newtype around it and represents a number of days elapsed since 1970-01-01. Also, time::Date is supported by using serde::time::date, that requires the time feature.
Example
```rust,ignore
struct MyRow { days: u16, #[serde(with = "clickhouse::serde::time::date")] date: Date, } ```
Date32 maps to/from i32 or a newtype around it and represents a number of days elapsed since 1970-01-01. Also, time::Date is supported by using serde::time::date32, that requires the time feature.
Example
```rust,ignore
struct MyRow { days: i32, #[serde(with = "clickhouse::serde::time::date32")] date: Date, } ```
DateTime maps to/from u32 or a newtype around it and represents a number of seconds elapsed since UNIX epoch. Also, time::OffsetDateTime is supported by using serde::time::datetime, that requires the time feature.
Example
```rust,ignore
struct MyRow { ts: u32, #[serde(with = "clickhouse::serde::time::datetime")] dt: OffsetDateTime, } ```
DateTime64(_) maps to/from i32 or a newtype around it and represents a time elapsed since UNIX epoch. Also, time::OffsetDateTime is supported by using serde::time::datetime64::*, that requires the time feature.
Example
```rust,ignore
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)
}
```
Typle(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 like Array((K, V)).LowCardinality(_) is supported seamlessly.Nullable(_) maps to/from Option<_>. For clickhouse::serde::* helpers add ::option.
Example
```rust,ignore
struct MyRow {
#[serde(with = "clickhouse::serde::ipv4::option")]
ipv4_opt: Option
Nested is supported by providing multiple arrays with renaming.
Example
```rust,ignore // CREATE TABLE test(items Nested(name String, count UInt32))
struct MyRow {
#[serde(rename = "items.name")]
itemsname: Vec
JSON and Geo aren't supported for now.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.