Rust SDK

Use the async Rust SDK from trusted backend applications and services.

Before you start

Use Rust 1.85 or later. Follow Connect an application, then set the endpoint and API key used by the examples:

export SCOPEDB_ENDPOINT="https://<endpoint>"
export SCOPEDB_API_KEY="<api-key>"

Install

cargo add scopedb-client serde_json
cargo add tokio --features macros,rt-multi-thread

Create a client

The SDK accepts a configured HTTP client. Use the compatible reqwest version re-exported by scopedb-client to configure authentication, TLS, timeouts, and connection pooling:

use scopedb_client::Client;
use scopedb_client::reqwest;
use scopedb_client::reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};

let endpoint = std::env::var("SCOPEDB_ENDPOINT")?;
let api_key = std::env::var("SCOPEDB_API_KEY")?;

let mut authorization = HeaderValue::from_str(&format!("Bearer {api_key}"))?;
authorization.set_sensitive(true);

let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, authorization);

let http_client = reqwest::Client::builder()
    .default_headers(headers)
    .build()?;
let client = Client::new(endpoint, http_client)?;

Marking the authorization header as sensitive prevents standard header and request Debug formatting from exposing the API key.

Execute a statement

let result = client
    .statement(
        r#"
        FROM events
        WHERE service = 'checkout'
        ORDER BY time DESC
        LIMIT 10
        "#
        .to_string(),
    )
    .execute()
    .await?;

execute() waits for a terminal result and returns an error when execution fails or is cancelled.

Work with results

Convert result cells to typed Rust values:

let fields = result.schema().fields();
println!("{} columns", fields.len());

let rows = result.into_values()?;
println!("{rows:?}");

Use json_rows() when the application needs unconverted string cell values instead of typed SDK values. See the data types reference for ScopeDB type semantics.

Control statement execution

Use submit() when you need a statement handle instead of waiting through execute():

let mut handle = client.statement("SELECT 1 AS ok".to_string()).submit().await?;

println!("status: {:?}", handle.status());
handle.fetch_once().await?;
let result = handle.fetch().await?;

Dropping the future stops client-side waiting; it does not cancel the remote statement. Call handle.cancel().await to request server-side cancellation. See the HTTP API for the underlying lifecycle and error contract.

Browse the catalog

List methods return one page and preserve the opaque continuation token. Fetch methods return a complete database, schema, or table resource:

use scopedb_client::CatalogListOptions;

let tables = client
    .list_tables("scopedb", "public", CatalogListOptions::default())
    .await?;
let table = client.fetch_table("scopedb", "public", "events").await?;

println!("{} tables; selected {}", tables.items.len(), table.name);

Stream JSON rows to a table

Use append_stream() when records already match an existing destination table. It serializes each object as NDJSON, creates bounded batches, and limits concurrent append requests:

use std::time::Duration;

let stream = client
    .table("events")
    .with_schema("public")
    .append_stream()
    .batch_bytes(4 * 1024 * 1024)
    .flush_interval(Duration::from_secs(1))
    .max_in_flight_requests(4)
    .build()?;

stream
    .send(&serde_json::json!({
        "service": "checkout",
        "name": "request.completed",
    }))
    .await?;

let report = stream.shutdown().await?;
println!("committed {} rows", report.committed_rows);

send() confirms local admission, not remote commit. flush() and shutdown() are remote delivery barriers. The in-memory stream is not a durable queue; use an outbox when rows must survive process failure.

Next steps