After building tinypw, I wanted a more complex project. And I was curious how to connect to Exasol from Rust. The result: exarrow-rs, an ADBC-compatible database driver that uses Apache Arrow’s columnar format.
Building it taught me more than expected: bridging async and sync APIs, navigating Arrow’s surprisingly deep type system, integrating with the ADBC ecosystem, and using spec-driven development with Claude Code to keep the implementation consistent.
Here’s what I built and what I learned.
Note: exarrow-rs started as a side-project prototype and is now maintained by Exasol Labs.
What it does#
exarrow-rs bridges Exasol databases and the Arrow ecosystem. Instead of row-by-row data transfer (slow for analytical queries), it uses Arrow’s columnar format to move data efficiently. The driver implements ADBC (Arrow Database Connectivity). Think ODBC/JDBC, but designed around Arrow from the ground up.
use exarrow_rs::Driver;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let user = "sys";
let password = "exasol";
let host = "localhost";
let port = 8563;
let driver = Driver::new();
let conn_str = format!("exasol://{user}:{password}@{host}:{port}");
let database = driver.open(&conn_str)?;
let connection = database.connect().await?;
let results = connection.query("SELECT * FROM schema.sales").await?;
// results is an Arrow RecordBatch - ready for analytics
Ok(())
}The interesting bits#
Fully async on Tokio. The driver communicates with Exasol over WebSockets using their native WebSocket API. This required bridging async I/O with ADBC’s synchronous interface.
Type-safe parameter binding. Rust’s type system ensures query parameters match expected types at compile time:
let stmt = connection.prepare("SELECT * FROM users WHERE id = ?").await?;
stmt.bind(0, &user_id)?;
let results = stmt.execute().await?;
Comprehensive type mapping. SQL types map to Arrow types, including edge cases:
| Exasol Type | Arrow Type |
|---|---|
BOOLEAN | Boolean |
VARCHAR | Utf8 |
DECIMAL(p,s) | Decimal128/Decimal256 |
TIMESTAMP | Timestamp(Microsecond) |
GEOMETRY | Binary (WKB) |
INTERVAL | Interval(MonthDayNano) |
C FFI layer. Build with --features ffi to get a shared library compatible with the ADBC driver manager. The adbc_core and adbc_ffi crates handle the C bindings, so you can load the driver from Python, Go, or any language with ADBC support.
One caveat#
The driver uses Exasol’s WebSocket API, which returns JSON responses. exarrow-rs converts these JSON responses into Arrow batches. It’s an extra conversion step, but the columnar output still integrates cleanly with Arrow-based tools.
What I learned#
Async + synchronous APIs are tricky. ADBC expects a blocking-style interface, but efficient database communication wants to be async. Bridging these two worlds while keeping the API ergonomic was an interesting challenge.
Arrow’s type system is extensive. The Arrow specification covers edge cases I’d never considered. Mapping SQL’s DECIMAL precision to Arrow’s Decimal128 vs Decimal256? Intervals with month, day, and nanosecond components? Each mapping taught me something about both ecosystems.
The ADBC ecosystem is well-designed. The adbc_core and adbc_ffi crates handle the C interop layer, so exposing the driver to other languages required implementing traits rather than writing unsafe FFI code.
Spec-driven development pays off. I built exarrow-rs using spec-driven development with Claude Code. Writing specifications before implementation kept the AI agent focused and reduced drift. Each feature had a clear spec, and the resulting code stayed consistent across the codebase.
What is Exasol?#
Exasol is a high-performance, in-memory analytical database designed for data warehousing and BI workloads. It’s an enterprise product, but offers a free personal edition for development and testing.
Summary#
If you want to find out more about exarrow-rs, checkout the source code and README on github.com/exasol-labs/exarrow-rs.