exarrow-rs 0.22.0

ADBC-compatible driver for Exasol with Arrow data format support
Documentation
[Home](index.md) · [Setup & Connect](setup-and-connect.md) · [Queries](queries.md) · [Prepared Statements](prepared-statements.md) · [Import/Export](import-export.md) · [Types](type-mapping.md) · [Driver Manager](driver-manager.md)

---

# Driver Manager Integration

exarrow-rs can be used with ADBC driver managers via its C FFI interface.

## Building the Driver

Enable the `ffi` feature to build the C-compatible shared library. The `ffi` feature automatically includes the native TCP transport:

```bash
cargo build --release --features ffi
```

This produces a shared library:
- Linux: `target/release/libexarrow_rs.so`
- macOS: `target/release/libexarrow_rs.dylib`
- Windows: `target/release/exarrow_rs.dll`

The shared library uses the **native TCP protocol** by default. To also include the WebSocket transport in the binary (allowing runtime selection via `transport=websocket` in the connection URI):

```bash
cargo build --release --features 'ffi websocket'
```

The ADBC Driver Foundry and other downstream build systems should use `--features ffi`, which ensures the native protocol is always compiled in.

## Connection URI

The connection URI follows the format:

```
exasol://user:password@host:port?tls=true&validateservercertificate=0
```

TLS is enabled by default and required for production Exasol instances. The `validateservercertificate=0` option disables certificate validation and should only be used with the Exasol Docker container for local development and testing. In production, omit this parameter to enforce proper certificate validation:

```
exasol://user:password@host:port?tls=true
```

### Credentials as database options

The username and password can also be passed as the ADBC `username` and `password` database options instead of in the URI:

```python
import adbc_driver_manager.dbapi

conn = adbc_driver_manager.dbapi.connect(
    driver="/path/to/libexarrow_rs.so",
    entrypoint="ExarrowDriverInit",
    db_kwargs={
        "uri": "exasol://localhost:8563?tls=true&validateservercertificate=0",
        "username": "user",
        "password": "p@ss?word",
    },
)
```

Option values need no URL-encoding: the driver uses them exactly as given. A set option replaces the matching credential in the URI, and an unset option leaves the URI credential in place. A connection without a username in the URI or in the `username` option fails with `Username is required`.

## Using with ADBC Driver Manager

### Python (adbc-driver-manager)

Install the dependencies:

```bash
pip install adbc-driver-manager pyarrow
```

Connect and query:

```python
import adbc_driver_manager.dbapi

conn = adbc_driver_manager.dbapi.connect(
    driver="/path/to/libexarrow_rs.so",
    entrypoint="ExarrowDriverInit",
    db_kwargs={"uri": "exasol://user:password@localhost:8563?tls=true&validateservercertificate=0"},
)

cursor = conn.cursor()
cursor.execute("SELECT * FROM my_table")
table = cursor.fetch_arrow_table()
print(table)

cursor.close()
conn.close()
```

DDL and DML statements (CREATE, INSERT, DROP, etc.) run through `cursor.execute`, and `cursor.rowcount` is -1 afterwards because ADBC `execute` does not report an affected-row count. The low-level `execute_update` returns the affected-row count:

```python
import adbc_driver_manager._lib as adbc_lib

stmt = adbc_lib.AdbcStatement(conn.adbc_connection)
stmt.set_sql_query("CREATE SCHEMA my_schema")
stmt.execute_update()
stmt.close()
```

### Parameters and row counts

Bind parameters with `cursor.execute` or `cursor.executemany`:

```python
cursor.execute("INSERT INTO t VALUES (?, ?)", (1, "a"))
print(cursor.rowcount)  # -1: ADBC execute does not report an affected-row count

cursor.executemany("INSERT INTO t VALUES (?, ?)", [(2, "b"), (3, "c"), (4, "d")])
print(cursor.rowcount)  # 3: the number of affected rows
```

A bound batch runs as one execution and stores all of its rows or none when its parameter values fit in the server's maximum data message size (64 MiB on the tested `exasol/docker-db` 2026.1.0 image). Over the native protocol, a larger batch runs as consecutive executions. With autocommit on, a failing execution then keeps the rows of the executions before it. A SELECT with several bound rows runs once per row, because Exasol rejects a multi-row parameter set for a statement that returns rows, and it returns the rows in bound order.

A bound batch needs several times its Arrow size in driver memory. For large loads, use bulk ingestion (`cursor.adbc_ingest`).

### Python (Polars)

Polars has native ADBC support, allowing direct queries into DataFrames:

```python
import polars as pl

# Query directly into a Polars DataFrame
df = pl.read_database_uri(
    query="SELECT * FROM my_table",
    uri="exasol://user:password@localhost:8563?tls=true&validateservercertificate=0",
    engine="adbc",
    engine_options={
        "driver": "/path/to/libexarrow_rs.so",
        "entrypoint": "ExarrowDriverInit",
    },
)

print(df)
```

For multiple queries, reuse the connection:

```python
import adbc_driver_manager.dbapi
import polars as pl

# Set up the connection once
conn = adbc_driver_manager.dbapi.connect(
    driver="/path/to/libexarrow_rs.so",
    entrypoint="ExarrowDriverInit",
    db_kwargs={"uri": "exasol://user:password@localhost:8563?tls=true&validateservercertificate=0"},
)

# Run multiple queries
df1 = pl.read_database(query="SELECT * FROM customers", connection=conn)
df2 = pl.read_database(query="SELECT * FROM orders", connection=conn)

# Join and analyze with Polars
result = df1.join(df2, on="customer_id").filter(pl.col("amount") > 100)
print(result)

conn.close()
```

### Rust (adbc-driver-manager)

Add the dependencies to your `Cargo.toml`:

```toml
[dependencies]
adbc_core = "0.24"
adbc_driver_manager = "0.24"
arrow = "58"
```

Load the driver, connect, and query:

```rust
use adbc_core::options::{AdbcVersion, OptionDatabase, OptionValue};
use adbc_core::{Connection, Database, Driver, Statement};
use adbc_driver_manager::ManagedDriver;
use arrow::array::RecordBatchReader;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load the shared library via the ADBC driver manager
    let mut driver = ManagedDriver::load_dynamic_from_filename(
        "target/release/libexarrow_rs.so", // .dylib on macOS, .dll on Windows
        Some(b"ExarrowDriverInit"),
        AdbcVersion::V110,
    )?;

    // Create a database with the connection URI
    let uri = "exasol://user:password@localhost:8563?tls=true&validateservercertificate=0";
    let opts = vec![(OptionDatabase::Uri, OptionValue::String(uri.into()))];
    let db = driver.new_database_with_opts(opts)?;

    // Open a connection and execute a SELECT query
    let mut conn = db.new_connection()?;
    let mut stmt = conn.new_statement()?;
    stmt.set_sql_query("SELECT * FROM my_table")?;
    let mut reader = stmt.execute()?;

    for batch in reader.by_ref() {
        let batch = batch?;
        println!("{} rows x {} cols", batch.num_rows(), batch.num_columns());
    }

    // Execute DDL/DML (no result set)
    let mut stmt = conn.new_statement()?;
    stmt.set_sql_query("CREATE SCHEMA my_schema")?;
    stmt.execute_update()?;

    Ok(())
}
```

See [`examples/driver_manager_usage.rs`](https://github.com/exasol-labs/exarrow-rs/blob/main/examples/driver_manager_usage.rs) for a complete example with error handling, DDL/DML workflows, and driver info queries.

### Other Languages

The driver follows the ADBC specification and works with any ADBC driver manager:
- [adbc-driver-manager (Python)](https://arrow.apache.org/adbc/current/python/index.html)
- [Go ADBC](https://pkg.go.dev/github.com/apache/arrow-adbc/go/adbc)
- [Java ADBC](https://arrow.apache.org/adbc/current/java/index.html)

## Examples

- **Python**: See [`examples/python/test_driver.py`](https://github.com/exasol-labs/exarrow-rs/blob/main/examples/python/test_driver.py) for a complete Python example with queries and DDL/DML workflows.
- **Rust**: See [`examples/driver_manager_usage.rs`](https://github.com/exasol-labs/exarrow-rs/blob/main/examples/driver_manager_usage.rs) for using the driver manager API from Rust.

## ADBC Functions

The FFI exports all standard ADBC entry points:

| Function | Description |
|----------|-------------|
| `ExarrowDriverInit` | Initialize the driver |
| `AdbcDatabaseNew` | Create a new database handle |
| `AdbcDatabaseSetOption` | Set database options (URI, etc.) |
| `AdbcDatabaseInit` | Initialize database with options |
| `AdbcConnectionNew` | Create a connection handle |
| `AdbcConnectionInit` | Open the connection |
| `AdbcStatementNew` | Create a statement handle |
| `AdbcStatementExecuteQuery` | Execute a query |