nusadb — native Rust driver + ORM for NusaDB
nusadb is a fast, stable, dependency-light Rust client for NusaDB. It speaks the
Nusa Wire Protocol (PROTOCOL_VERSION 1.1) directly over TCP — it
does not link the engine, use C bindings, or pull in an async runtime.
- Fast — buffered framing, native value decoding, pooled connections,
nodelaysockets. - Stable — no panics in the library path, bounds-checked decoding, constant-time SCRAM verification, explicit error type.
- Complete — a driver is a SQL-text transport, so it supports every query the server accepts: any DDL/DML/SELECT, joins, CTEs, window functions, subqueries, set-ops, transactions, stored procedures/functions, and all 16 wire value types decoded to native Rust.
Standalone crate: it has its own
[workspace], so it builds and publishes independently of the NusaDB engine workspace.
Install
[]
= "0.1" # sync, plaintext (default)
# nusadb = { version = "0.1", features = ["tls"] } # + sync TLS
# nusadb = { version = "0.1", features = ["async"] } # + Tokio async API
# nusadb = { version = "0.1", features = ["async-tls"] } # + async over TLS
Features
| feature | adds |
|---|---|
| (default) | sync, plaintext driver + pool + ORM |
tls |
sync TLS 1.3 (rustls / ring) |
async |
async API on Tokio (AsyncConnection, AsyncPool) |
async-tls |
async + TLS (async + tls + tokio-rustls) |
Quick start
use Connection;
let mut conn = connect?;
conn.execute?;
conn.query_params?;
let result = conn.query?;
for row in &result.rows
# Ok::
Parameters & types
Parameters are positional ($1, $2, …) and bind any ToSql type; Option<T> is NULL:
conn.query_params?;
Results decode to native Rust via the protocol-1.1 typed column metadata:
| NusaDB type | Value variant |
read via row.get::<T>() |
|---|---|---|
BOOL |
Value::Bool |
bool |
INT |
Value::Int |
i64 |
FLOAT |
Value::Float |
f64 |
NUMERIC |
Value::Numeric |
String / f64 |
TEXT |
Value::Text |
String |
BYTES |
Value::Bytes |
Vec<u8> |
DATE/TIME/TIMESTAMP(TZ)/INTERVAL/UUID/JSON/ARRAY/VECTOR |
Value::Typed { tag, text } |
String |
result.column_types exposes each column's TypeTag (like JDBC getColumnTypeName).
BYTEA round-trips as raw bytes: a Vec<u8> / &[u8] parameter binds as the \x<hex> text form
(coerced into the BYTEA column), and a BYTES column decodes back to Value::Bytes / Vec<u8>.
Prepared statements
let stmt = conn.prepare?;
let a = conn.query_prepared?;
let b = conn.query_prepared?;
Transactions
conn.transaction?; // COMMIT on Ok, ROLLBACK on Err
Inside an open transaction, savepoint(name) marks a point you can later undo to with
rollback_to_savepoint(name) (the transaction stays open) or forget with
release_savepoint(name). The async AsyncConnection has the same three methods.
Notifications (LISTEN/NOTIFY)
listen(channel) subscribes the connection; a notify(channel, payload) from any connection on the
same database is then delivered asynchronously. poll_notification(timeout) waits for the next one
(a buffered one returns immediately; None blocks), or notifications() drains those buffered while
reading other responses. AsyncConnection has the same methods.
conn.listen?;
// ... elsewhere: other.notify("orders", Some("42"))?;
if let Some = conn.poll_notification?
conn.unlisten?;
Connection pool
use ;
let pool = new?;
let mut conn = pool.get?; // blocks if the pool is saturated
let n = conn.query?.rows.len;
// `conn` returns to the pool on drop.
ORM / query builder
A thin, native builder (double-quoted identifiers, $n params, constant LIMIT/OFFSET,
RETURNING *). Covers the full SELECT surface — DISTINCT, joins, comparison/IN/LIKE/
BETWEEN/NULL predicates, GROUP BY + HAVING, set operations (UNION/INTERSECT/EXCEPT),
and aggregate terminals. Drop to raw SQL any time.
use ;
use ;
into.set.set.run?;
let rows = from
.columns
.filter
.order_by
.limit
.offset // pagination — constant OFFSET, inlined for the server
.fetch?;
// DISTINCT, joins (inner/left/right/full/cross), and aggregate terminals.
let cities = from.distinct.columns.fetch?;
let joined = from
.columns
.inner_join
.fetch?;
let n = from.count?; // also: sum / avg / min / max
let avg_id = from.avg?;
// Predicates, GROUP BY + HAVING, and set operations.
let adults = from
.gte
.where_in
.is_not_null
.fetch?;
let by_city = from
.select_raw
.group_by
.having
.fetch?;
let combined = from.columns
.union
.order_by
.fetch?;
table.set.filter.run?;
from.filter.run?;
// Map rows into your own structs:
let users: = from.fetch_as?;
TLS (feature tls)
The Nusa Wire Protocol uses implicit TLS 1.3 (rustls, ring provider): the client opens TLS
immediately, then handshakes inside the encrypted stream. Use the nusadbs:// scheme, or set
Config::tls:
use ;
// Public roots (Mozilla/webpki):
let mut conn = connect?;
// Private CA:
let mut cfg = from_url?;
cfg.tls = Some;
let mut conn = connect_config?;
// Dev only — skip verification (never in production):
cfg.tls = Some;
# Ok::
Async (feature async)
AsyncConnection / AsyncPool mirror the sync API on Tokio (add async-tls for TLS):
# async
Authentication
- Trust (dev/local): no password — pass
password: None. - SCRAM-SHA-256 (RFC 7677): set
password; the driver runs the full SASL exchange and verifies the server's signature in constant time (mutual auth).
Cancellation
let handle = conn.cancel_handle; // (pid, secret) from BackendKeyData
// from another thread/connection, to abort an in-flight statement:
handle.cancel?;
Bulk load / export (COPY)
copy_in / copy_out stream the COPY sub-protocol — one round-trip
for the whole dataset instead of a row per insert. You write the COPY statement (with any
WITH (...) options) and move bytes in the server's text format: tab-delimited fields, \N for
SQL NULL, newline per row.
use Cursor;
// Bulk load: stream any Read of text-format rows.
let mut rows = new;
let loaded = conn.copy_in?;
assert_eq!;
// Bulk export: collect the rows into any Write.
let mut out = Vecnew;
let exported = conn.copy_out?;
# Ok::
If the source fails mid-load the driver sends CopyFail, and a COPY the server refuses (bad SQL,
an RLS-protected table) returns an error — either way the connection is left ready for the next
statement. AsyncConnection exposes the same copy_in / copy_out over Tokio (an AsyncRead
source, an AsyncWrite sink) with the async feature.
Status & roadmap
Supported today: trust + SCRAM auth, TLS 1.3 (feature tls), simple & extended
(parameterized/prepared) queries, all value types, transactions (BEGIN/COMMIT/ROLLBACK),
connection pooling, cancellation, COPY FROM/TO (bulk load/export, sync and async), the ORM
builders, and a full async API on Tokio (features async / async-tls).
Not yet: binary result formats (text is used throughout — lossless for every type). An additive follow-up; it does not change the surface above.
Testing
License
Apache-2.0.