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(())
}
NoneorSome(":memory:")→ in-memorySome("path")→ file-backed
Executing Queries¶
run: Execute and discard results¶
Use this for CREATE, MERGE, SET, etc.
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.
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:
NullBool(bool)Integer(i64)Float(f64)Text(&[u8])Json(&[u8])
Transactions¶
Velr supports fully nested write transactions with savepoints.
Opening a transaction¶
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¶
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¶
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_dfinside 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:
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 tablesTableResult→ column names + rowsRowIterCellRef