# picodata-driver
## Description
`picodata-driver` is an asynchronous Rust driver for interacting with a Picodata cluster based on PostgreSQL. It provides load balancing across nodes, automatic topology discovery, shard-aware query routing, and connection pooling.
## Installation
Add the following to your `Cargo.toml`:
```toml
[dependencies]
picodata-driver = { version = "3.0.0" }
```
Enable TLS support if needed:
```toml
[dependencies]
picodata-driver = { version = "3.0.0", features = ["tls"] }
```
## Quick Start
```rust
use std::time::Duration;
use picodata_driver::{Config, connect_cfg, Client, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut pg_cfg = pg::Config::new();
pg_cfg.host("127.0.0.1");
pg_cfg.user("user");
pg_cfg.password("password");
pg_cfg.dbname("mydb");
let cfg = Config {
postgres: pg_cfg,
pool_max_size: 10,
discorery_interval: Duration::from_secs(1),
..Default::default()
};
let client: Client = connect_cfg(cfg).await?;
let rows = client.query(
"SELECT id, name FROM users WHERE active = $1",
&[&true]
).await?;
for row in rows {
let id: i32 = row.get("id");
let name: &str = row.get("name");
println!("User {}: {}", id, name);
}
client.close().await?;
Ok(())
}
```
## Shard-Aware Routing
The driver automatically routes queries to the correct replicaset based on distribution key metadata received from the server via `pico_query_metadata` Notice messages.
```rust
client.execute(
r#"INSERT INTO "users" ("id", "name") VALUES ($1, $2)"#,
&[&42i64, &"alice"]
).await?;
client.query("SELECT * FROM users WHERE id = $1", &[&42i64])
.on_master(false)
.await?;
client.execute("INSERT INTO users (id, name) VALUES ($1, $2)", &[&1i64, &"bob"])
.bucket_id(42)
.await?;
client.query("SELECT * FROM users", &[])
.instance("default_1_1")
.await?;
```
`.instance(name)` returns a pinned query that takes neither `.on_master` nor `.bucket_id`.
## Topology
`Client::topology` returns the cluster tiers with their replicasets, instances and bucket ranges. `Tier::bucket_map` maps each replicaset UUID to its ranges and fails with `Error::IncompleteBucketMap` if the ranges leave a gap in the tier's buckets.
```rust
let topology = client.topology().await?;
for tier in topology.tiers() {
let bucket_map = tier.bucket_map()?;
for rs in tier.replicasets() {
let master = rs.master().map(|i| i.name());
println!("{} {} master={master:?}", tier.name(), rs.uuid());
for inst in rs.instances() {
let iproto = inst.address("iproto");
println!(" {} iproto={iproto:?}", inst.name());
}
for range in bucket_map[&rs.uuid()] {
println!(" buckets {}..={}", range.start(), range.end());
}
}
}
```
Each instance carries an address per connection type it advertises in `_pico_peer_address`. `Instance::address` takes the type name, `"iproto"` or anything `AsRef<str>`; queries go over `pgproto`.
## TLS Configuration Example
```rust
use std::time::Duration;
use picodata_driver::{Config, connect_cfg, Client, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut pg_cfg = pg::Config::new();
pg_cfg.host("127.0.0.1");
pg_cfg.user("user");
pg_cfg.password("password");
pg_cfg.dbname("mydb");
let cfg = Config {
postgres: pg_cfg,
pool_max_size: 10,
discorery_interval: Duration::from_secs(1),
ssl_cert_file: Some("/path/to/client.crt".into()),
ssl_key_file: Some("/path/to/client.key".into()),
ssl_ca_file: Some("/path/to/ca-cert.pem".into()),
..Default::default()
};
let client: Client = connect_cfg(cfg).await?;
client.close().await?;
Ok(())
}
```
## Custom Load Balancing Strategy
By default, `RoundRobin` is used, but you can supply any strategy implementing the `Strategy` trait:
```rust
use picodata_driver::{Client, Config, Random};
let client = Client::new_with_strategy(cfg, Random).await?;
```