neo4j/lib.rs
1// Copyright Rouven Bauer
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![allow(clippy::option_map_unit_fn)]
16
17//! # Neo4j Bolt Driver
18//!
19//! This crate provides a driver for the Neo4j graph database.
20//! It's designed to mirror many concepts of the official drivers while leveraging Rust's expressive
21//! type system and lifetime management to provide a safer API that prevents many common pitfalls
22//! already at compile time.
23//!
24//! ## Compatibility
25// [bolt-version-bump] search tag when changing bolt version support
26//! This driver supports bolt protocol version 4.4, and 5.0 - 5.8.
27//! This corresponds to Neo4j versions 4.4, and the whole 5.x series.
28//! Newer versions of Neo4j are supported as long as they keep support for at least one of the
29//! protocol versions mentioned above.
30//! For details of bolt protocol compatibility, see the
31//! [official Neo4j documentation](https://7687.org/bolt-compatibility/).
32//!
33//! ## Basic Example
34//! ```
35//! use std::sync::Arc;
36//!
37//! use neo4j::address::Address;
38//! use neo4j::driver::auth::AuthToken;
39//! use neo4j::driver::{ConnectionConfig, Driver, DriverConfig, RoutingControl};
40//! use neo4j::retry::ExponentialBackoff;
41//! use neo4j::{value_map, ValueReceive};
42//!
43//! let host = "localhost";
44//! # let host = doc_test_utils::get_host();
45//! let port = 7687;
46//! # let port = doc_test_utils::get_port();
47//! let user = "neo4j";
48//! # let user = doc_test_utils::get_user();
49//! let password = "pass";
50//! # let password = doc_test_utils::get_password();
51//! let database = "neo4j";
52//!
53//! let database = Arc::new(String::from(database));
54//! let address = Address::from((host, port));
55//! let auth_token = AuthToken::new_basic_auth(user, password);
56//! let driver = Driver::new(
57//! // tell the driver where to connect to
58//! ConnectionConfig::new(address),
59//! // configure how the driver works locally (e.g., authentication)
60//! DriverConfig::new().with_auth(Arc::new(auth_token)),
61//! );
62//!
63//! // Driver::execute_query() is the easiest way to run a query.
64//! // It will be sufficient for most use-cases and allows the driver to apply some optimizations.
65//! // So it's recommended to use it whenever possible.
66//! // For more control, see sessions and transactions.
67//! let result = driver
68//! // Run a CYPHER query against the DBMS.
69//! .execute_query("RETURN $x AS x")
70//! // Always specify the database when you can (also applies to using sessions).
71//! // This will let the driver work more efficiently.
72//! .with_database(database)
73//! // Tell the driver to send the query to a read server.
74//! // In a clustered environment, this will make sure that read queries don't overload
75//! // the single write server.
76//! .with_routing_control(RoutingControl::Read)
77//! // Use query parameters (instead of string interpolation) to avoid injection attacks and
78//! // improve performance.
79//! .with_parameters(value_map!({"x": 123}))
80//! // For more resilience, use retry policies.
81//! // Especially in a clustered environment, this will help to recover from transient errors
82//! // like those caused by leader elections, which are to be expected.
83//! .run_with_retry(ExponentialBackoff::default());
84//! println!("{:?}", result);
85//!
86//! let result = result.unwrap();
87//! assert_eq!(result.records.len(), 1);
88//! for mut record in result.records {
89//! assert_eq!(record.values().count(), 1);
90//! assert_eq!(record.take_value("x"), Some(ValueReceive::Integer(123)));
91//! }
92//! ```
93//!
94//! ## Concepts
95//!
96//! ### The Driver
97//! The fundamental type of this crate is the [`Driver`].
98//! Through it, all database interactions are performed.
99//! See [`Driver::new()`].
100//! The driver manages a connection pool. So there is no need to pool driver objects.
101//! Usually, each application will use one global driver.
102//!
103//! ### Sessions
104//! Sessions are spawned from the driver.
105//! See [`Driver::session()`].
106//! Session creation is cheap, it's recommended to create a new session for each piece of work
107//! (unless when using [`Driver::execute_query()`]).
108//! Sessions will borrow connections from the driver's pool as needed.
109//!
110//! ### Main Mechanisms for Query Execution
111//! There are three main ways to execute queries:
112//! - [`Driver::execute_query()`] is the easiest way to run a query.
113//! Prefer it whenever possible as it most efficient.
114//! - [`Session::transaction()`] gives you full control over the transaction.
115//! - [`Session::auto_commit()`] is a special method for running queries that manage their own
116//! transactions, such as `CALL {...} IN TRANSACTION`.
117//!
118//! ### Causal Consistency
119//! By default, Neo4j clusters are eventually consistent:
120//! a write transaction executed on the leader (write node) will sooner or later be visible to read
121//! transactions on all followers (read nodes).
122//! To provide stronger guarantees, the server sends a bookmark to the client after every
123//! successful transaction that applies a write.
124//! These bookmarks are abstract tokens that represent some state of the database.
125//! By passing them back to the server along with a transaction, the client requests the server to
126//! wait until the state(s) represented by the bookmark(s) have been established before executing
127//! the transaction.
128//!
129//! To point out the obvious: relying on bookmarks can be slow because of the wait described above.
130//! Not using them, however, can lead to stale reads which will be unacceptable in some cases.
131//!
132//! See also [`Bookmarks`].
133//!
134//! #### Methods for Managing Bookmarks
135//! * The easiest way is to rely on the fact that [`Session`]s will automatically manage
136//! bookmarks for you.
137//! All work run in the same session will be part of the same causal chain.
138//! * Manually passing [`Bookmarks`] between sessions.
139//! See [`Session::last_bookmarks()`] for an example.
140//! * Using a [`BookmarkManager`], which [`Driver::execute_query`] does by default.
141//! See [`SessionConfig::with_bookmark_manager()`], [`Driver::execute_query_bookmark_manager()`].
142//!
143//! ## Logging
144//! The driver uses the [`log`] crate for logging.
145//!
146//! **Important Notes on Usage:**
147//! * Log messages are *not* considered part of the driver's API.
148//! They may change at any time and don't follow semantic versioning.
149//! * The driver's logs are meant for debugging the driver itself.
150//! Log levels `ERROR` and `WARN` are used liberally to indicate (potential) problems within
151//! abstraction layers of the driver.
152//! If there are problems the user-code needs to be aware of, they will be reported via
153//! [`Result`]s, not log messages.
154//!
155//! ### Logging Example
156//! ```
157//! use std::sync::Arc;
158//!
159//! use env_logger; // example using the env_logger crate
160//! use log;
161//! use neo4j::driver::{Driver, RoutingControl};
162//! use neo4j::retry::ExponentialBackoff;
163//!
164//! # use doc_test_utils::get_driver;
165//!
166//! env_logger::builder()
167//! .filter_level(log::LevelFilter::Debug)
168//! .init();
169//!
170//! let driver: Driver = get_driver();
171//! driver
172//! .execute_query("RETURN 1")
173//! .with_database(Arc::new(String::from("neo4j")))
174//! .with_routing_control(RoutingControl::Read)
175//! .run_with_retry(ExponentialBackoff::new())
176//! .unwrap();
177//! ```
178
179mod address_;
180pub mod driver;
181mod error_;
182mod macros;
183mod sync;
184#[cfg(feature = "_internal_testkit_backend")]
185pub mod time;
186#[cfg(not(feature = "_internal_testkit_backend"))]
187mod time;
188mod util;
189pub mod value;
190
191// imports for docs
192#[allow(unused)]
193use bookmarks::{BookmarkManager, Bookmarks};
194#[allow(unused)]
195use driver::record_stream::RecordStream;
196#[allow(unused)]
197use driver::Driver;
198#[allow(unused)]
199use session::{Session, SessionConfig};
200
201pub use error_::{Neo4jError, Result};
202pub use value::ValueReceive;
203pub use value::ValueSend;
204
205/// Address and address resolution.
206pub mod address {
207 pub use super::address_::resolution::*;
208 pub use super::address_::*;
209}
210/// Bookmarks for [causal consistency](crate#causal-consistency).
211pub mod bookmarks {
212 pub use super::driver::session::bookmarks::*;
213}
214/// Error and result types.
215pub mod error {
216 pub use super::error_::{
217 GqlErrorCause, GqlErrorClassification, ServerError, UserCallbackError,
218 };
219}
220/// Retry policies.
221pub mod retry {
222 pub use super::driver::session::retry::*;
223}
224/// Session and session configuration.
225pub mod session {
226 pub use super::driver::session::*;
227}
228/// Query summary structs (metadata) received via [`RecordStream::consume()`].
229pub mod summary {
230 pub use super::driver::summary::*;
231}
232/// Transactions and associated types.
233pub mod transaction {
234 pub use super::driver::transaction::*;
235}
236
237mod private {
238 // Trait to prevent traits from being implemented outside of this crate.
239 #[allow(dead_code)]
240 pub trait Sealed {}
241}
242
243#[cfg(test)]
244mod test {
245 #[cfg(feature = "public-api")]
246 #[test]
247 fn public_api() {
248 // Install a compatible nightly toolchain if it is missing
249 rustup_toolchain::install(public_api::MINIMUM_NIGHTLY_RUST_VERSION).unwrap();
250
251 // Build rustdoc JSON
252 let rustdoc_json = rustdoc_json::Builder::default()
253 .toolchain(public_api::MINIMUM_NIGHTLY_RUST_VERSION)
254 .build()
255 .unwrap();
256
257 // Derive the public API from the rustdoc JSON
258 let public_api = public_api::Builder::from_rustdoc_json(rustdoc_json)
259 .omit_blanket_impls(true)
260 .build()
261 .unwrap();
262
263 // Assert that the public API looks correct
264 expect_test::expect_file!["test_data/public-api.txt"].assert_eq(&public_api.to_string());
265 }
266}