picodata-driver 3.0.0

A native asyncronous cluster-aware Picodata client
Documentation

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:

[dependencies]
picodata-driver = { version = "3.0.0" }

Enable TLS support if needed:

[dependencies]
picodata-driver = { version = "3.0.0", features = ["tls"] }

Quick Start

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.

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.

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

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:

use picodata_driver::{Client, Config, Random};

let client = Client::new_with_strategy(cfg, Random).await?;