Install clickhouse-rs
async-awaitAdd clickhouse-rs to your Cargo.toml dependencies to use the asynchronous ClickHouse client in your Rust project.
[dependencies]
clickhouse-rs = "*"repository·async-await·Indexed 43 days ago
https://github.com/suharev7/clickhouse-rsAn asynchronous ClickHouse client library for Rust (version 1.1.0-alpha.1) providing high-performance access to Yandex ClickHouse. It supports both Tokio and async-std runtimes, TLS via native-tls or rustls, and provides a Pool-based connection management system for executing DDL statements, inserting data via Blocks, and fetching query results.
Add clickhouse-rs to your Cargo.toml dependencies to use the asynchronous ClickHouse client in your Rust project.
[dependencies]
clickhouse-rs = "*"The library uses optional features to optimize compile times. You must choose between tokio_io and async_std, as they are mutually exclusive.
In clickhouse-rs, a Column<K> is a type-safe wrapper around ArcColumnData.
Column<K>: The primary user-facing structure. The generic parameter K is a ColumnType that acts as a marker for the expected SQL type.ColumnType: A trait used to mark valid column types. The library provides Simple and Complex markers.ArcColumnData: An atomically reference-counted container that holds the actual raw data and implements the logic for data access, casting, and iteration.This design allows columns to be cloned efficiently (via Arc) while maintaining type safety through the K marker.
A Block represents a collection of columns in ClickHouse, organized by rows. It is the primary container for data being sent to or received from the server. You can construct blocks manually, add columns, or push rows using a builder pattern.
Key capabilities:
usize) or by name (&str or String)..get::<T>(row, col) to retrieve a specific value from a cell, where T must implement FromSql..rows() to get an iterator over the rows in the block..push(row) with a RowBuilder (like the row! macro) to append data..chunks(n) to split a large block into smaller blocks of size n.// Creating a block and adding columns
let block = Block::<Simple>::new()
.column("hello_id", vec![5_u32, 6_u32])
.column("value", vec!["lol", "zuz"]);
// Accessing a specific cell
let val: u32 = block.get(0, "hello_id").unwrap();
// Iterating over rows
for row in block.rows() {
let id: u32 = row.get("hello_id").unwrap();
}The client uses a URL schema to define connection parameters. The format is:
schema://user:password@host[:port]/database?param1=value1&...¶mN=valueN
| Parameter | Description | Default |
|---|---|---|
compression | Compression algorithm. Options: none, lz4 | none |
connection_timeout | Timeout for establishing a connection | 500 ms |
query_timeout | Timeout for queries | 180 sec |
insert_timeout | Timeout for inserts | 180 sec |
execute_timeout | Timeout for execute operations | 180 sec |
keepalive | TCP keep alive timeout in milliseconds | N/A |
nodelay | Whether to enable TCP_NODELAY | true |
pool_min | Lower bound of opened connections for Pool | 10 |
pool_max | Upper bound of opened connections for Pool | 20 |
ping_before_query | Ping server before executing any query | true |
send_retries | Count of retries to send request to server | 3 |
retry_timeout | Wait time before next retry | 5 sec |
ping_timeout | Timeout for ping operations | 500 ms |
alt_hosts | Comma separated list of single address hosts for load-balancing | N/A |
Example URL:
tcp://user:password@host:9000/clicks?compression=lz4&ping_timeout=42ms
tcp://user:password@host:9000/clicks?compression=lz4&ping_timeout=42msThe recommended way to interact with ClickHouse is to create a Pool and obtain a ClientHandle.
Pool with a connection URL.pool.get_handle().await? to get a ClientHandle.client.execute(sql).await? for statements like CREATE TABLE.client.insert(table, block).await? to insert a Block of data.client.query(sql).fetch_all().await? to retrieve results as a Block.block.rows() to iterate through the returned rows and row.get(column_name)? to extract values.use clickhouse_rs::{Block, Pool, errors::Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
let ddl = "CREATE TABLE IF NOT EXISTS payment (customer_id UInt32, amount UInt32, account_name Nullable(FixedString(3))) Engine=Memory";
let block = Block::new()
.column("customer_id", vec![1_u32, 3, 5, 7, 9])
.column("amount", vec![2_u32, 4, 6, 8, 10])
.column("account_name", vec![Some("foo"), None, None, None, Some("bar")]);
let pool = Pool::new("tcp://localhost:9000?compression=lz4");
let mut client = pool.get_handle().await?;
client.execute(ddl).await?;
client.insert("payment", block).await?;
let block = client.query("SELECT * FROM payment").fetch_all().await?;
for row in block.rows() {
let id: u32 = row.get("customer_id")?;
let amount: u32 = row.get("amount")?;
let name: Option<&str> = row.get("account_name")?;
println!("Found payment {id}: {amount} {name:?}");
}
Ok(())
}# use std::env;
# use clickhouse_rs::{Block, Pool, errors::Error};
#
# #[tokio::main]
# async fn main() -> Result<(), Error> {
# let ddl = r"
# CREATE TABLE IF NOT EXISTS payment (
# customer_id UInt32,
# amount UInt32,
# account_name Nullable(FixedString(3))
# ) Engine=Memory";
#
# let block = Block::new()
# .column("customer_id", vec![1_u32, 3, 5, 7, 9])
# .column("amount", vec![2_u32, 4, 6, 8, 10])
# .column("account_name", vec![Some("foo"), None, None, None, Some("bar")]);
#
# let pool = Pool::new("tcp://localhost:9000?compression=lz4".into());
#
# let mut client = pool.get_handle().await?;
# client.execute(ddl).await?;
# client.insert("payment", block).await?;
# let block = client.query("SELECT * FROM payment").fetch_all().await?;
#
# for row in block.rows() {
# let id: u32 = row.get("customer_id")?;
# let amount: u32 = row.get("amount")?;
# let name: Option<&str> = row.get("account_name")?;
# println!("Found payment {id}: {amount} {name:?}");
# }
# Ok(())
# }To run a ClickHouse server instance for local development or testing with clickhouse-rs, you can use the provided docker-compose.yml configuration. This setup uses the yandex/clickhouse-server image and exposes the following ports:
8123: HTTP interface (typically used for REST API interactions).9000: Native TCP interface (typically used by high-performance drivers like clickhouse-rs).version: '3'
services:
clickhouse:
image: yandex/clickhouse-server
ports:
- 8123:8123
- 9000:9000You can optimize compile times and runtime behavior by enabling specific features:
tokio_io (default): Uses Tokio for asynchronous I/O.async_std: Uses async-std for asynchronous I/O (cannot be used with tokio_io).tls-native-tls: TLS support using native-tls (requires tokio_io).tls-rustls: TLS support using rustls (requires tokio_io).Note: tls-native-tls and tls-rustls are mutually exclusive.
To interact with ClickHouse, use a Pool to manage connections. You can create a Block to prepare data for insertion, execute DDL statements, and fetch query results as blocks.
use clickhouse_rs::{Block, Pool};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let database_url = "tcp://user:password@host:9000/db";
let ddl = r"
CREATE TABLE IF NOT EXISTS payment (
customer_id UInt32,
amount UInt32,
account_name Nullable(FixedString(3))
) Engine=Memory";
// Prepare data using Block
let block = Block::new()
.column("customer_id", vec![1_u32, 3, 5, 7, 9])
.column("amount", vec![2_u32, 4, 6, 8, 10])
.column("account_name", vec![Some("foo"), None, None, None, Some("bar")]);
let pool = Pool::new(database_url);
// Get a handle from the pool and perform operations
let mut client = pool.get_handle().await?;
client.execute(ddl).await?;
client.insert("payment", block).await?;
// Query data
let block = client.query("SELECT * FROM payment").fetch_all().await?;
// Iterate through rows and extract values
for row in block.rows() {
let id: u32 = row.get("customer_id")?;
let amount: u32 = row.get("amount")?;
let name: Option<&str> = row.get("account_name")?;
println!("Found payment {}: {} {:?}", id, amount, name);
}
Ok(())
}The Pool configuration is derived from the Options object passed to Pool::new(). You can control the connection lifecycle using the following settings:
pool_min: The minimum number of idle connections to maintain in the pool (defaults to 5 if not specified).pool_max: The maximum number of total connections allowed (defaults to 10 if not specified).addr: The primary connection address.alt_hosts: A list of alternative hosts for load balancing/failover. The pool uses a round-robin approach via get_addr() to distribute connections across these hosts.Note: Ensure you are using the binary protocol port (typically 9000). If you attempt to connect via port 8123 (the HTTP/text protocol), a warning will be issued as clickhouse-rs is designed for the binary protocol.
use clickhouse_rs::{Pool, Options};
use std::str::FromStr;
// Example with custom pool limits and multiple hosts
let options = Options::from_str("tcp://host1:9000?alt_hosts=host2:9000,host3:9000&pool_min=6&pool_max=12")
.unwrap();
let pool = Pool::new(options);The client connects using a URL with the following schema:
schema://user:password@host[:port]/database?param1=value1&...¶mN=valueN
| Parameter | Description | Default |
|---|---|---|
compression | Compression algorithm. Choices: none, lz4. | none |
readonly | Restricts permissions. 0: All allowed, 1: Read only, 2: Read and change settings. | 0 |
connection_timeout | Timeout for establishing a connection. | 500 ms |
keepalive | TCP keep alive timeout in milliseconds. | N/A |
nodelay | Whether to enable TCP_NODELAY. | true |
pool_min | Lower bound of opened connections for Pool. | 10 |
pool_max | Upper bound of opened connections for Pool. | 20 |
ping_before_query | Ping server before executing any query. | true |
send_retries | Number of retries to send a request. | 3 |
retry_timeout | Time to wait before the next retry. | 5 sec |
ping_timeout | Timeout for the ping operation. | 500 ms |
alt_hosts | Comma-separated list of addresses for load-balancing. | N/A |
Example URL:
tcp://user:password@host:9000/clicks?compression=lz4&ping_timeout=42ms
tcp://user:password@host:9000/clicks?compression=lz4&ping_timeout=42msThe clickhouse-rs client supports the following data types:
UInt8, UInt16, UInt32, UInt64, UInt128, Int8, Int16, Int32, Int64, Int128, Float32, Float64, Decimal(P, S)String, FixedString(N)Date, DateTimeIPv4, IPv6, UUID, BoolNullable(T)Array(UInt/Int/Float/String/Date/DateTime)SimpleAggregateFunction(F, T)