Skip to content

Rust Driver

A lightweight, embedded Rust API for querying, mutating, and integrating with the Velr graph database.

The Rust driver exposes:

  • Connection management (Velr::open)
  • Executing Cypher queries (exec, exec_one, run)
  • Transactional control (begin_tx, VelrTx)
  • Savepoints (savepoint, savepoint_named)
  • Streaming result tables (ExecTables, TableResult)
  • Binding external data into Cypher via BIND(...) (rows, Arrow, and Polars)
  • Dropping binds (drop_all_binds)

Opening a Database

use velr::Velr;

fn main() -> Result<(), String> {
    // In-memory database
    let db = Velr::open(None)?;

    // Or on-disk
    let db = Velr::open(Some("my.db"))?;

    Ok(())
}
  • None or Some(":memory:") → in-memory
  • Some("path") → file-backed

Executing Queries

run: Execute and discard results

Use this for CREATE, MERGE, SET, etc.

db.run("CREATE (:User {name:'Alice'})")?;

exec: Stream one or more result tables

Use this for queries returning rows.

let mut stream = db.exec("MATCH (u:User) RETURN u.name AS name")?;

while let Some(mut table) = stream.next_table()? {
    table.for_each_row(|row| {
        if let CellRef::Text(name) = row[0] {
            println!("User: {}", std::str::from_utf8(name).unwrap());
        }
        Ok(())
    })?;
}

exec_one: Expect exactly one result table

Fails if query yields 0 or >1 tables.

let mut table = db.exec_one(
    "MATCH (u:User) RETURN u.name AS name, u.age AS age"
)?;

Reading Results

A TableResult gives you:

  • Column names: column_names()
  • Row iteration: rows(), for_each_row(...)
  • Mapping into typed structs: via collect(...)

Example:

let mut table = db.exec_one(
    "MATCH (m:Movie) RETURN m.title AS title, m.released AS year"
)?;

println!("{:?}", table.column_names());

table.for_each_row(|row| {
    let title = match row[0] {
        CellRef::Text(t) => std::str::from_utf8(t).unwrap(),
        _ => "<invalid>"
    };
    let year = match row[1] {
        CellRef::Integer(y) => y,
        _ => -1
    };
    println!("{title} ({year})");
    Ok(())
})?;

Supported CellRef types:

  • Null
  • Bool(bool)
  • Integer(i64)
  • Float(f64)
  • Text(&[u8])
  • Json(&[u8])

Transactions

Velr supports fully nested write transactions with savepoints.

Opening a transaction

let tx = db.begin_tx()?;

Running queries inside a transaction

tx.run("CREATE (:Person {name:'Neo'})")?;

let mut t = tx.exec_one(
    "MATCH (p:Person) RETURN count(p) AS c"
)?;

Commit / Rollback

tx.commit()?;
// or
tx.rollback()?;

If you drop a VelrTx without committing, Velr automatically rolls it back.


Savepoints

Savepoints allow partial rollback inside a transaction.

Auto-named savepoint

let sp = tx.savepoint()?;

tx.run("CREATE (:Temp {k:'x'})")?;

// rollback only this part
sp.rollback()?;

Named savepoint

let sp = tx.savepoint_named("before_write")?;
sp.release()?;   // or sp.rollback()?

Binding External Data with BIND()

The driver can create temporary virtual tables that Cypher can read using UNWIND BIND('name').

These bindings are zero-copy and extremely fast.

Supported bindings:

  • Row tables (bind_cells / bind_cells_tx)
  • Apache Arrow arrays (bind_arrow, bind_arrow_tx)
  • Apache Arrow chunked columns (bind_arrow_chunks, bind_arrow_chunks_tx)
  • Polars DataFrames (bind_polars_df / bind_polars_df inside a tx)

Bindings work at both connection-scope and transaction-scope.


1. Binding Simple Rows

Example: Binding a small values table

use velr::{Velr, OwnedCell};

let db = Velr::open(None)?;

let columns = vec!["name".into(), "age".into()];
let rows = vec![
    vec![OwnedCell::text("Alice"), OwnedCell::integer(30)],
    vec![OwnedCell::text("Bob"),   OwnedCell::integer(25)],
];

db.bind_cells("_people", columns, rows)?;

db.run("
    UNWIND BIND('_people') AS r
    CREATE (:Person {name: r.name, age: r.age})
")?;

2. Binding Arrow Arrays

Requires arrow-ipc feature.

let cols = vec!["name".into(), "active".into()];
let arrays: Vec<Box<dyn Array>> = vec![
    Utf8Array::<i64>::from(vec![Some("Alice"), Some("Bob")]).boxed(),
    BooleanArray::from(vec![Some(true), Some(false)]).boxed(),
];

db.bind_arrow("_users", cols, arrays)?;

db.run("
    UNWIND BIND('_users') AS r
    CREATE (:User {name:r.name, active:r.active})
")?;

Chunked Arrow binding

db.bind_arrow_chunks(
    "_chunked",
    vec!["value".into()],
    vec![vec![
        PrimitiveArray::<i64>::from([Some(1), Some(2)]).boxed(),
        PrimitiveArray::<i64>::from([Some(3)]).boxed(),
    ]]
)?;

3. Binding Polars DataFrames

Requires polars feature.

let df = df![
    "name" => &["Alice", "Bob"],
    "score" => &[10, 20]
]?;

db.bind_polars_df("_df", &df)?;

db.run("
    UNWIND BIND('_df') AS r
    CREATE (:Player {name:r.name, score:r.score})
")?;

Dropping Binds

If you create many binds, you can clear them:

db.drop_all_binds()?;

Transaction-scoped binds are automatically dropped when the transaction ends.


Complete Example

use velr::Velr;

fn main() -> Result<(), String> {
    let db = Velr::open(None)?;

    // Create graph
    db.run("CREATE (:Movie {title:'Inception', released:2010})")?;

    // Query
    let mut t = db.exec_one(
        "MATCH (m:Movie) RETURN m.title AS title, m.released AS year"
    )?;

    t.for_each_row(|row| {
        let title = std::str::from_utf8(match row[0] {
            CellRef::Text(t) => t,
            _ => b"<??>",
        }).unwrap();

        let year = match row[1] {
            CellRef::Integer(y) => y,
            _ => -1,
        };

        println!("{title} ({year})");
        Ok(())
    })?;

    Ok(())
}

API Summary

Velr

  • open(path: Option<&str>)
  • run(cypher)
  • exec(cypher)
  • exec_one(cypher)
  • begin_tx()
  • bind_cells(name, cols, rows)
  • bind_arrow(name, cols, arrays) (feature: arrow-ipc)
  • bind_arrow_chunks(name, cols, chunks) (feature: arrow-ipc)
  • bind_polars_df(name, df) (feature: polars)
  • drop_all_binds()

VelrTx

  • exec(cypher)
  • exec_one(cypher)
  • run(cypher)
  • commit()
  • rollback()
  • savepoint()
  • savepoint_named(name)
  • rollback_to(name)
  • bind_cells_tx(...)
  • bind_arrow_tx(...)
  • bind_arrow_chunks_tx(...)
  • bind_polars_df(...)

Result Types

  • ExecTables → stream of tables
  • TableResult → column names + rows
  • RowIter
  • CellRef