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}