Crate bolt_client
source ·Expand description
This crate contains a runtime-agnostic asynchronous client for graph database servers that support the Bolt protocol.
The central feature of this library is the Client
struct, which allows sending Bolt
messages to a compatible server. Clients can operate over any type that implements
AsyncRead
and AsyncWrite
.
If you want to connect to a Bolt-compatible server from your application, you probably want to use a connection pool - see bb8-bolt, deadpool-bolt, or mobc-bolt.
If you’d rather manage your own connections, an asynchronous TCP/TLS Stream
wrapper is also
available, if you’re using the tokio runtime.
Features
tokio-stream
- enables theStream
type
Example
The below example demonstrates how to communicate with a Neo4j server using Bolt protocol version 4.
use std::{collections::HashMap, env};
use tokio::io::BufStream;
use tokio_util::compat::*;
use bolt_client::*;
use bolt_proto::{message::*, value::*, version::*, Message, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Let's say you have a type that implements AsyncRead + AsyncWrite. Here's one
// provided by the `tokio-stream` feature of this library. In this example, all
// connection/authentication details are stored in environment variables.
let stream = Stream::connect(env::var("BOLT_TEST_ADDR")?,
env::var("BOLT_TEST_DOMAIN").ok()).await?;
// Be sure to buffer your IO :)
let stream = BufStream::new(stream).compat();
// Create a new connection to the server and perform a handshake to establish a
// protocol version. This example demonstrates usage of the v4.3 or v4.2 protocol.
let mut result = Client::new(stream, &[V4_3, V4_2, 0, 0]).await;
let mut client = result.unwrap();
// Send a HELLO message with authentication details to the server to initialize
// the session.
let response: Message = client.hello(
Metadata::from_iter(vec![
("user_agent", "my-client-name/1.0"),
("scheme", "basic"),
("principal", &env::var("BOLT_TEST_USERNAME")?),
("credentials", &env::var("BOLT_TEST_PASSWORD")?),
])).await?;
assert!(Success::try_from(response).is_ok());
// Submit a query for execution on the server
let response = client.run("RETURN 1 as num;", None, None).await?;
// Successful responses will include a SUCCESS message with related metadata
// Consuming these messages is optional and will be skipped for the rest of the example
assert!(Success::try_from(response).is_ok());
// Use PULL to retrieve results of the query, organized into RECORD messages
// We get a (Vec<Record>, Message) returned from a PULL
let pull_meta = Metadata::from_iter(vec![("n", 1)]);
let (records, response) = client.pull(Some(pull_meta.clone())).await?;
assert_eq!(records[0].fields(), &[Value::from(1)]);
// Submit a more complex query with parameters
let params = Params::from_iter(vec![("name", "Rust")]);
client.run(
"CREATE (:Client)-[:WRITTEN_IN]->(:Language {name: $name});",
Some(params), None).await?;
client.pull(Some(pull_meta.clone())).await?;
// Grab a node from the database and convert it to a native type
client.run("MATCH (rust:Language) RETURN rust;", None, None).await?;
let (records, response) = client.pull(Some(pull_meta.clone())).await?;
let node = Node::try_from(records[0].fields()[0].clone())?;
// Access properties from returned values
assert_eq!(node.labels(), &[String::from("Language")]);
assert_eq!(node.properties(),
&HashMap::from_iter(vec![(String::from("name"), Value::from("Rust"))]));
// End the connection with the server
client.goodbye().await?;
Ok(())
}
For version 3 of the protocol, the metadata we pass to Client::pull
is not required, since
all records are consumed.
// Now we only want Bolt v3
let mut result = Client::new(stream, &[V3_0, 0, 0, 0]).await;
// Use `None` for the PULL metadata
let (records, response) = client.pull(None).await?;
For versions 1 and 2 of the protocol, there are a couple more differences:
// For the handshake, we can support versions 1 and 2 only, preferring version 2.
let mut result = Client::new(stream, &[V2_0, V1_0, 0, 0]).await;
// No need to pass metadata here either
let (records, response) = client.pull(None).await?;
// There is no call to `goodbye`
See the documentation of the Client
struct for information on transaction management, error
handling, and more.
Re-exports
pub use bolt_proto;