TypeDB Rust Driver
Driver Architecture
To learn about the mechanism that TypeDB drivers use set up communication with databases running on the TypeDB Server,
refer to the Drivers Overview.
The TypeDB Rust Driver provides a fully async API that supports multiple async runtimes or a synchronous interface gated
by the sync feature.
API Reference
To learn about the methods available for executing queries and retrieving their answers using Rust, refer to
the API Reference.
Install TypeDB Rust Driver through Cargo
- Import
typedb-driver through Cargo:
cargo add typedb-driver
- Make sure a TypeDB Server is
running.
- Use TypeDB Driver in your program (see Example usage or
tests/integration for examples):
use typedb_driver::TypeDBDriver;
let driver = TypeDBDriver::new_core(TypeDBDriver::DEFAULT_ADDRESS).await.unwrap();
Build from Source
Note: You don't need to compile TypeDB Driver from source if you just want to use it in your code. See the "
Quickstart" section above.
-
Make sure you have Bazel via Bazelisk installed on your machine.
-
Build the library:
a) to build the native/raw rlib:
bazel build //rust:typedb_driver
The rlib will be produced at: bazel-bin/libtypedb_driver-{hash}.rlib.
b) to build the crate for a Cargo project:
bazel build //rust:assemble_crate
The Cargo crate will be produced at:
bazel-bin/assemble_crate.crate
You can then unzip this crate to retrieve Cargo.toml. Please note: this process has not yet been thoroughly
tested. The generated Cargo.toml may not be fully correct. See the Cargo.toml of the typedb-driver crate for
reference.
Example usage
use std::time::Duration;
use futures::{StreamExt, TryStreamExt};
use typedb_driver::{
answer::{
concept_document::{Leaf, Node},
ConceptRow, QueryAnswer,
},
concept::{Concept, ValueType},
Credentials, DriverOptions, Error, QueryOptions, TransactionOptions, TransactionType, TypeDBDriver,
};
fn typedb_example() {
async_std::task::block_on(async {
let driver = TypeDBDriver::new(
TypeDBDriver::DEFAULT_ADDRESS,
Credentials::new("admin", "password"),
DriverOptions::new(false, None).unwrap(),
)
.await
.unwrap();
driver.databases().create("typedb").await.unwrap();
let database = driver.databases().get("typedb").await.unwrap();
{
let transaction = driver.transaction(database.name(), TransactionType::Read).await.unwrap();
let result = transaction.query("define entity i-cannot-be-defined-in-read-transactions;").await;
match result {
Ok(_) => println!("This line will not be printed"),
Err(error) => match error {
Error::Connection(connection) => println!("Could not connect: {connection}"),
Error::Server(server) => {
println!("Received a detailed server error regarding the executed query: {server}")
}
Error::Internal(_) => panic!("Unexpected internal error"),
_ => println!("Received an unexpected external error: {error}"),
},
}
}
let options = TransactionOptions::new().transaction_timeout(Duration::from_secs(10));
let transaction =
driver.transaction_with_options(database.name(), TransactionType::Schema, options).await.unwrap();
let define_query = r#"
define
entity person, owns name, owns age;
attribute name, value string;
attribute age, value integer;
"#;
let answer = transaction.query(define_query).await.unwrap();
if answer.is_ok() && matches!(answer, QueryAnswer::Ok(_)) {
println!("OK results do not give any extra interesting information, but they mean that the query is successfully executed!");
}
transaction.commit().await.unwrap();
let transaction = driver.transaction(database.name(), TransactionType::Read).await.unwrap();
let answer = transaction.query("match entity $x;").await.unwrap();
let rows: Vec<ConceptRow> = answer.into_rows().try_collect().await.unwrap();
let row = rows.get(0).unwrap();
let column_names = row.get_column_names();
let column_name = column_names.get(0).unwrap();
let concept_by_name = row.get(column_name).unwrap().unwrap();
let concept_by_index = row.get_index(0).unwrap().unwrap();
if concept_by_name.is_entity_type() {
print!("Getting concepts by variable names and indexes is equally correct. ");
println!(
"Both represent the defined entity type: '{}' (in case of a doubt: '{}')",
concept_by_name.get_label(),
concept_by_index.get_label()
);
}
let answer = transaction.query("match attribute $a;").await.unwrap();
let mut rows_stream = answer.into_rows();
while let Some(Ok(row)) = rows_stream.next().await {
let mut column_names_iter = row.get_column_names().into_iter();
let column_name = column_names_iter.next().unwrap();
let concept_by_name = row.get(column_name).unwrap().unwrap();
if concept_by_name.is_attribute_type() {
let label = concept_by_name.get_label();
let value_type = concept_by_name.try_get_value_type().unwrap();
println!("Defined attribute type's label: '{label}', value type: '{value_type}'");
}
println!("It is also possible to just print the concept itself: '{}'", concept_by_name);
}
let transaction = driver.transaction(database.name(), TransactionType::Write).await.unwrap();
let answer = transaction
.query("insert $z isa person, has age 10; $x isa person, has age 20, has name \"John\";")
.await
.unwrap();
let mut rows = Vec::new();
let mut rows_stream = answer.into_rows();
while let Some(Ok(row)) = rows_stream.next().await {
rows.push(row);
}
let row = rows.get(0).unwrap();
for column_name in row.get_column_names() {
let inserted_concept = row.get(column_name).unwrap().unwrap();
println!("Successfully inserted ${}: {}", column_name, inserted_concept);
if inserted_concept.is_entity() {
println!("This time, it's an entity, not a type!");
}
}
let column_names = row.get_column_names();
let x = row.get_index(column_names.iter().position(|r| r == "x").unwrap()).unwrap().unwrap();
if let Some(iid) = x.try_get_iid() {
println!("Each entity receives a unique IID. It can be retrieved directly: {}", iid);
}
transaction.commit().await.unwrap();
let transaction = driver.transaction(database.name(), TransactionType::Write).await.unwrap();
let queries = ["insert $a isa person, has name \"Alice\";", "insert $b isa person, has name \"Bob\";"];
for query in queries {
transaction.query(query);
}
transaction.commit().await.unwrap();
{
let transaction = driver.transaction(database.name(), TransactionType::Write).await.unwrap();
let queries =
["insert $c isa not-person, has name \"Chris\";", "insert $d isa person, has name \"David\";"];
let mut promises = vec![];
for query in queries {
promises.push(transaction.query(query));
}
let result = transaction.commit().await;
println!("Commit result will contain the unresolved query's error: {}", result.unwrap_err());
}
let transaction = driver.transaction(database.name(), TransactionType::Read).await.unwrap();
let options = QueryOptions::new().include_instance_types(true);
let var = "x";
let answer = transaction.query_with_options(format!("match ${} isa person;", var), options).await.unwrap();
let mut count = 0;
let mut stream = answer.into_rows().map(|result| result.unwrap());
while let Some(row) = stream.next().await {
let x = row.get(var).unwrap().unwrap();
match x {
Concept::Entity(x_entity) => {
let x_type = x_entity.type_().unwrap();
count += 1;
println!("Found a person {} of type {}", x, x_type);
}
_ => unreachable!("An entity is expected"),
}
}
println!("Total persons found: {}", count);
let fetch_query = r#"
match
$x isa! person, has $a;
$a isa! $t;
fetch {
"single attribute type": $t,
"single attribute": $a,
"all attributes": { $x.* },
};
"#;
let answer = transaction.query(fetch_query).await.unwrap();
let mut count = 0;
let mut stream = answer.into_documents().map(|result| result.unwrap());
while let Some(document) = stream.next().await {
match document.root.as_ref().unwrap() {
Node::Map(map) => {
println!("Found a map document:\n{{");
for (parameter_name, node) in map {
print!(" \"{parameter_name}\": ");
match node {
Node::Map(map) => println!("map of {} element(s)", map.len()),
Node::Leaf(leaf) => match leaf.as_ref().unwrap() {
Leaf::Concept(concept) => match concept {
Concept::AttributeType(type_) => println!("attribute type '{}'", type_.label()),
Concept::Attribute(attribute) => println!("attribute '{}'", attribute.value),
_ => unreachable!("Unexpected concept is fetched"),
},
_ => unreachable!("Unexpected leaf type is fetched"),
},
_ => unreachable!("Expected lists in inner maps"),
}
print!("")
}
println!("}}");
}
_ => unreachable!("Unexpected document type is fetched"),
}
count += 1;
println!("JSON representation of the fetched document:\n{}", document.into_json().to_string());
}
println!("Total documents fetched: {}", count);
println!("More examples can be found in the API reference and the documentation.\nWelcome to TypeDB!");
})
}
Logging
The TypeDB Rust driver includes comprehensive logging functionality using the tracing crate. This allows you to monitor driver operations, debug issues, and understand the driver's behavior.
Logging Configuration
The logging level can be controlled through environment variables with the following priority:
- TYPEDB_DRIVER_LOG - Driver-specific log level (highest priority)
- RUST_LOG - General Rust logging level (fallback)
- Default level: INFO - If no environment variables are set
The logging is scoped to the typedb_driver package only, so it won't affect other loggers like Tonic or other dependencies. This means you can control TypeDB driver logging independently from your application's other logging.
If you want to track the memory exchanges between Rust and the C layer, you can set TYPEDB_DRIVER_CLIB_LOG to TRACE.
Supported Log Levels
error - Only error messages
warn - Warning and error messages
info - Informational, warning, and error messages (default)
debug - Detailed debugging information
trace - Very detailed tracing information
Examples
TYPEDB_DRIVER_LOG=debug cargo run
RUST_LOG=info cargo run
cargo run
Manual Initialization
If you need to initialize logging before creating a driver (for example, to see connection logs), you can call the initialization function directly:
use typedb_driver::TypeDBDriver;
TypeDBDriver::init_logging();
For more detailed logging configuration and integration with application logging, see the Logging Documentation.