ClickHouse Rust client

You could use [ClickHouse asynchronous inserts](/docs/optimize/asynchronous-inserts) to avoid client-side batching of the incoming data. This can be done by simply providing the `async_insert` option to the `insert` method (or even to the `Client` instance itself, so that it will affect all the `insert` calls).

```
let client = Client::default()
    .with_url("http://localhost:8123")
    .with_option("async_insert", "1")
    .with_option("wait_for_async_insert", "0");
```

See also:

- [Async insert example](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/async_insert.rs) in the client repo.

### Inserter feature (client-side batching)[​](#inserter-feature-client-side-batching "Direct link to Inserter feature (client-side batching)")

Requires the `inserter` cargo 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,
    );
}

// don't forget to finalize the inserter during the application shutdown
// and commit the remaining rows. `.end()` will provide stats as well.
inserter.end().await?;
```

- `Inserter` ends the active insert in `commit()` if any of the thresholds (`max_bytes`, `max_rows`, `period`) are reached.
- The interval between ending active `INSERT`s can be biased by using `with_period_bias` to avoid load spikes by parallel inserters.
- `Inserter::time_left()` can be used to detect when the current period ends. Call `Inserter::commit()` again to check limits if your stream emits items rarely.
- Time thresholds implemented by using [quanta](https://docs.rs/quanta) crate to speed the `inserter` up. Not used if `test-util` is enabled (thus, time can be managed by `tokio::time::advance()` in custom tests).
- All rows between `commit()` calls are inserted in the same `INSERT` statement.

Note

Don't forget to flush if you want to terminate/finalize inserting:

```
inserter.end().await?;
```

### Executing DDLs[​](#executing-ddls "Direct link to Executing DDLs")

With a single-node deployment, it is enough to execute DDLs like this:

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

However, on clustered deployments with a load-balancer or ClickHouse Cloud, it is recommended to wait for the DDL to be applied on all the replicas, using the `wait_end_of_query` option. This can be done like this:

```
client
    .query("DROP TABLE IF EXISTS some")
    .with_option("wait_end_of_query", "1")
    .execute()
    .await?;
```

### ClickHouse settings[​](#clickhouse-settings "Direct link to ClickHouse settings")

You can apply various [ClickHouse settings](/docs/operations/settings/settings) using the `with_option` method. For example:

```
let numbers = client
    .query("SELECT number FROM system.numbers")
    // This setting will be applied to this particular query only;
    // it will override the global client setting.
    .with_option("limit", "3")
    .fetch_all::<u64>()
    .await?;
```

Besides `query`, it works similarly with `insert` and `inserter` methods; additionally, the same method can be called on the `Client` instance to set global settings for all queries.

### Query ID[​](#query-id "Direct link to Query ID")

Using `.with_option`, you can set the `query_id` option to identify queries in the ClickHouse query log.

```
let numbers = client
    .query("SELECT number FROM system.numbers LIMIT 1")
    .with_option("query_id", "some-query-id")
    .fetch_all::<u64>()
    .await?;
```

Besides `query`, it works similarly with `insert` and `inserter` methods.

Danger

If you set `query_id` manually, make sure that it is unique. UUIDs are a good choice for this.

See also: [query\_id example](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/query_id.rs) in the client repo.

### Session ID[​](#session-id "Direct link to Session ID")

Similarly to `query_id`, you can set the `session_id` to execute the statements in the same session. `session_id` can be set either globally on the client level, or per `query`, `insert`, or `inserter` call.

```
let client = Client::default()
    .with_url("http://localhost:8123")
    .with_option("session_id", "my-session");
```

Danger

With clustered deployments, due to lack of "sticky sessions", you need to be connected to a *particular cluster node* in order to properly utilize this feature, cause, for example, a round-robin load-balancer won't guarantee that the consequent requests will be processed by the same ClickHouse node.

See also: [session\_id example](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/session_id.rs) in the client repo.

### Custom HTTP headers[​](#custom-http-headers "Direct link to Custom HTTP headers")

If you're using proxy authentication or need to pass custom headers, you can do it like this:

```
let client = Client::default()
    .with_url("http://localhost:8123")
    .with_header("X-My-Header", "hello");
```

See also: [custom HTTP headers example](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/custom_http_headers.rs) in the client repo.

### Custom HTTP client[​](#custom-http-client "Direct link to Custom HTTP client")

This could be useful for tweaking the underlying HTTP connection pool settings.

```
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;

let connector = HttpConnector::new(); // or HttpsConnectorBuilder
let hyper_client = HyperClient::builder(TokioExecutor::new())
    // For how long keep a particular idle socket alive on the client side (in milliseconds).
    // It is supposed to be a fair bit less that the ClickHouse server KeepAlive timeout,
    // which was by default 3 seconds for pre-23.11 versions, and 10 seconds after that.
    .pool_idle_timeout(Duration::from_millis(2_500))
    // Sets the maximum idle Keep-Alive connections allowed in the pool.
    .pool_max_idle_per_host(4)
    .build(connector);

let client = Client::with_http_client(hyper_client).with_url("http://localhost:8123");
```

Note

This example relies on the legacy Hyper API and is a subject to change in the future.

See also: [custom HTTP client example](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/custom_http_client.rs) in the client repo.

## Data types[​](#data-types "Direct link to Data types")

References

See also the additional examples:

- [Simpler ClickHouse data types](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/data_types_derive_simple.rs)
- [Container-like ClickHouse data types](https://github.com/ClickHouse/clickhouse-rs/blob/main/examples/data_types_derive_containers.rs)

- `(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](https://github.com/ClickHouse/clickhouse-rs/issues/48).
- `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`](https://github.com/loyd/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`](https://docs.rs/smartstring/latest/smartstring/struct.SmartString.html). New types are also supported. To store bytes, consider using [`serde_bytes`](https://docs.rs/serde_bytes/latest/serde_bytes/), because it's more efficient.