dbkit-rs
Multi-backend database infrastructure for Rust applications.
Transactional writes go through [sqlx]'s Any driver — the backend (Postgres,
MySQL, or SQLite) is chosen by the connection URL scheme. Analytical reads go
through a pluggable, Arrow-based engine (DuckDB or DataFusion) behind feature
flags.
Features
- Multi-backend writes —
ConnectionManager+BaseHandlerover sqlx;postgres://,mysql://, orsqlite://selects the backend - Connection pooling — auto-create database, configurable pool size and timeouts
- Configurable —
DbkitConfigbuilder with URL construction, SSL modes, and pool tuning - Unified writes —
execute_writewithWriteOp(single / batch DDL / batch params) and neutralDbValueparameters - Analytical reads —
execute_readreturnsVec<RecordBatch>(Arrow);execute_read_as::<T>deserializes rows into typed structs viaserde_arrow - Pluggable read engines — DuckDB or DataFusion (both may be enabled together; they share an Arrow version)
- Transactional → analytical sync —
sync_tables()/sync_query()copy data into the read engine (any backend × any engine); DuckDB can also attach Postgres live - Backend-aware migrations —
InitializationHandlerwith named migrations tracked by content hash, portable across Postgres/MySQL/SQLite - Concurrent cache — generic DashMap-based key-value cache with named buckets
- Unicode normalization —
BaseHandler::normalize_name()for consistent name matching via NFD decomposition
Usage
use ;
// Connect — the URL scheme picks the backend (postgres:// / mysql:// / sqlite://)
let conn = new.await?;
// Or use the config builder
let config = builder
.host
.database
.user
.password
.pool_size
.build;
let conn = connect.await?;
// `pool()` yields a sqlx `AnyPool` (cheaply cloneable)
let handler = new;
// Write. Placeholders are backend-native: `$1` for Postgres, `?` for MySQL/SQLite.
handler.execute_write.await?;
Migrations
InitializationHandler tracks named migrations by content hash in a
_dbkit_migrations table whose DDL adapts to the backend.
use InitializationHandler;
let init = new;
init.run_named_migration.await?;
The tracking table is portable, but the migration SQL you supply is backend-native — write it for the database you connected to.
Pool health
let status = conn.pool_status;
println!;
Analytical reads (optional)
Enable a read engine. DuckDB and DataFusion share the Arrow RecordBatch
contract, so either can be used the same way:
= { = "0.3", = ["duckdb"] } # or "datafusion"
Attach a read engine to the handler:
// DuckDB (in-memory, bundled)
let handler = with_duckdb?;
// or DataFusion (pure-Rust, no FFI)
let handler = with_datafusion?;
Sync, then read
Copy transactional tables into the analytical engine, then query them:
use DbValue;
// Whole tables
handler.sync_tables.await?;
// A filtered / arbitrary query, loaded as a named table
handler.sync_query.await?;
// Arrow read
let batches = handler.execute_read.await?;
// ...or deserialize straight into typed rows
let orders: =
handler.execute_read_as.await?;
Live Postgres attach (DuckDB)
DuckDB can query Postgres tables directly — no copy — via the pg catalog:
let handler = with_duckdb_attached_postgres?;
let batches = handler.execute_read.await?;
Rich Postgres types (uuid / timestamps / json)
The multi-backend Any pool only represents basic scalars (bool / int / float /
text / bytes) — the price of Postgres/MySQL/SQLite interchangeability. For
queries involving native Postgres types (uuid, timestamptz, jsonb, arrays,
decimal), enable postgres-native and drop to a real PgPool with full sqlx
type support:
= { = "0.3", = ["postgres-native"] }
let pg = conn.pg_native_pool.await?; // native PgPool, rich types
let row = query
.bind
.fetch_one
.await?;
let id: Uuid = row.get;
let ts: DateTime = row.get;
Use the Any pool (handler.execute_write) for portable work, and the native
pool only where you need Postgres-specific types. (Other type features such as
rust_decimal can be enabled by adding sqlx to your own Cargo.toml with the
relevant feature — cargo unifies it with dbkit's.)
Name normalization
let normalized = normalize_name;
assert_eq!;
Cache
Both key and value types are generic, defaulting to String:
use Cache;
let cache: Cache = with_buckets;
cache.set;
let val = cache.get;
// Typed keys and values
let counts: = with_buckets;
counts.set;
assert_eq!;
Feature flags
| Feature | Default | Description |
|---|---|---|
postgres |
on | Postgres backend (sqlx) |
postgres-native |
off | Native PgPool with full Postgres types (uuid/chrono/json) — see below |
mysql |
off | MySQL backend (sqlx) |
sqlite |
off | SQLite backend (sqlx) |
duckdb |
off | DuckDB analytical read engine (bundled) + typed reads |
datafusion |
off | DataFusion analytical read engine (pure-Rust) + typed reads |
duckdb and datafusion currently share an Arrow version and may be enabled
together.
License
MIT