Rust SDK for YDB.
Rust 1.88.0 or newer
CI checks compatibility on Rust 1.88 and Rust 1.96. Linting, publishing, and SLO workload builds use Rust 1.96.
Add the YDB dependency to your project using cargo add ydb or add this your Cargo.toml:
[dependencies]
ydb = "0.18.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Create a new Rust file (e.g., main.rs) and add the following code:
use ydb::{ClientBuilder, YdbResult};
#[tokio::main]
async fn main() -> YdbResult<()> {
let client = ClientBuilder::new_from_connection_string("grpc://localhost:2136/local")?
.build()
.await?;
let mut qc = client.query_client();
// one-shot: retries internally, no closure for a single statement
let mut row = qc.query_row("SELECT 1 + 1 AS sum").await?;
let sum: i32 = row.remove_field_by_name("sum")?.try_into()?;
println!("sum: {sum}");
Ok(())
}For more examples, see ydb/examples.
For a single YQL statement you usually do not need retry_tx — call a builder and .await?:
| Method | Returns | Use for |
|---|---|---|
exec |
() |
DDL, DML without rows (CREATE TABLE, UPSERT, DELETE) |
query_row |
one Row |
exactly one row (SELECT COUNT(*) …) |
query_result_set |
one ResultSet |
all rows of one result set |
query |
QueryStream |
multiple result sets, large reads |
Parameters chain at the call site:
use ydb::{ydb_params, ClientBuilder, YdbResult};
#[tokio::main]
async fn main() -> YdbResult<()> {
let client = ClientBuilder::new_from_connection_string("grpc://localhost:2136/local")?
.build()
.await?;
let mut qc = client.query_client();
qc.exec("CREATE TABLE IF NOT EXISTS test (id Int64, val Utf8, PRIMARY KEY(id))")
.await?;
qc.exec(
"UPSERT INTO test (id, val) VALUES ($id, $val)",
)
.param("$id", 1_i64)
.param("$val", "hello")
.await?;
// or: .params(ydb_params!("$id" => 2_i64, "$val" => "world"))
let mut row = qc.query_row("SELECT COUNT(*) AS cnt FROM test").await?;
let cnt: i64 = row.remove_field_by_name("cnt")?.try_into()?;
println!("cnt = {cnt}");
Ok(())
}Use .optional() when zero rows is OK, .typed::<T>() to map a row into your struct (see query-service-basic).
For multi-statement atomic work, use QueryClient::retry_tx with async |tx: &mut Transaction| { … } (see query-service-transaction).
New project
- Start local YDB from the repository root:
docker compose up -d - Add
ydbandtokiotoCargo.toml(see Installation). - Copy the Example or run the SDK example:
cd ydb cargo run --example query-service-basic
Migrating from table_client
Replace client.table_client() with client.query_client() and simplify call sites:
| Table API | Query API |
|---|---|
execute_scheme_query(sql) |
qc.exec(sql).await? |
retry_tx + one t.query(...) |
one-shot: qc.exec(...) / qc.query_row(...) / qc.query_result_set(...) |
retry_tx + several statements |
qc.retry_tx + tx.exec(...) (see example below) |
Query::from(sql).with_params(...) |
qc.exec(sql).params(ydb_params!(...)).await? or .param("$name", value) |
res.into_only_row()? |
qc.query_row(sql).await? |
res.into_only_result()?.rows() |
qc.query_result_set(sql).await? |
Notes:
- One-shot calls use implicit sessions and server-side transaction mode by default (DDL — non-transactional,
SELECT— snapshot read-only, DML — serializable read-write). table_clientremains available for legacy code; new code should preferquery_client.- Full before/after: compare
basic-select-upsert.rs(table) withquery-service-basic.rs(query).
Integration tests, with dependency from real YDB database marked as ignored. To run it:
- Set YDB_CONNECTION_STRING env
- run cargo test -- --include-ignored
Crates follow to semver 2.0 https://semver.org/spec/v2.0.0.html. For version 0.X.Y: X increments for expected backwards incompatible changes, Y increments for any compatible changes (fixes, extend api without broke compatible). For incompatible changes creates github release with describe incompatibles.