stargate_grpc/
lib.rs

1//! # Rust gRPC Client Driver for Stargate
2//!
3//! This crate provides a high-level async Rust driver
4//! for querying [Stargate](https://stargate.io/).
5//!
6//! ## Features
7//! - All of the Stargate gRPC protocol messages exposed as Rust structures and enums
8//! - Token-based authentication
9//! - Asynchronous querying
10//! - Query builder with easy binding of variables by names or positions
11//! - Optional compile-time type-checking of query bind values
12//! - Easy conversions between gRPC value types and common Rust types; support for
13//!   primitive types, lists, maps, tuples and user-defined-types, with arbitrary nesting levels
14//! - Result set paging
15//!
16//! ## Usage
17//! Add required dependencies.
18//!
19//! ```toml
20//! [dependencies]
21//! stargate-grpc = "0.1"
22//! tokio = { version = "1", features = ["full"]}
23//! ```
24//!
25//!
26//! ### Establishing the connection
27//! The main structure that provides the interface to Stargate is [`StargateClient`].
28//! The simplest way to obtain an instance is to use the provided
29//! [`builder`](StargateClient::builder):
30//!
31//! ```
32//! use std::str::FromStr;
33//! use stargate_grpc::client::{default_tls_config, AuthToken, StargateClient};
34//!
35//! # async fn connect() -> anyhow::Result<()>{
36//! let uri = "http://localhost:8090/";                    // Stargate URI
37//! let token = "00000000-0000-0000-0000-000000000000";    // Stargate authentication token
38//! let token = AuthToken::from_str(token)?;
39//! let mut client = StargateClient::builder()
40//!     .uri(uri)?
41//!     .auth_token(token)
42//!     .tls(Some(default_tls_config()?))                  // optionally to enable TLS
43//!     .connect()
44//!     .await?;
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! If you want to control the properties of the connection which are not exposed by the builder,
50//! add [`tonic`](https://docs.rs/tonic/0.5.2/tonic/) to the dependencies of the project and create
51//! the connection manually. Then use [`StargateClient::with_auth`] to wrap the connection and
52//! the authentication token:
53//!
54//! ```
55//! # use std::str::FromStr;
56//! use std::time::Duration;
57//! # use stargate_grpc::client::{default_tls_config, AuthToken, StargateClient};
58//! #
59//! # async fn connect() -> anyhow::Result<()>{
60//! # let uri = "http://localhost:8090";
61//! # let token = "00000000-0000-0000-0000-000000000000";
62//! # let token = AuthToken::from_str(token).unwrap();
63//! let channel = tonic::transport::Endpoint::new(uri)?
64//!     .connect_timeout(Duration::from_secs(30))
65//!     .tcp_nodelay(true)
66//!     .connect().await?;
67//! let mut client = StargateClient::with_auth(channel, token);
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! ### Querying
73//! Call [`Query::builder`] to set a CQL string, bind query arguments
74//! set query parameters and finally produce a `Query`:
75//!
76//! ```rust
77//! use stargate_grpc::{Consistency, Query};
78//! let query = Query::builder()
79//!     .keyspace("test")                              // set the keyspace the query applies to
80//!     .consistency(Consistency::LocalQuorum)         // set consistency level
81//!     .query("SELECT * FROM users WHERE id = :id")   // set CQL query text (required)
82//!     .bind_name("id", 1000)                         // bind :id to 1000
83//!     .build();                                      // build the Query
84//! ```
85//!
86//! Run the query and wait for its results:
87//! ```rust
88//! # use std::convert::TryInto;
89//! # use stargate_grpc::{StargateClient, Query};
90//! # async fn run_query(client: &mut StargateClient, query: Query) -> anyhow::Result<()> {
91//! use stargate_grpc::ResultSet;
92//! let response = client.execute_query(query).await?; // send the query and wait for gRPC response
93//! let result_set: ResultSet = response.try_into()?;  // convert the response into ResultSet
94//! # Ok(())
95//! # }
96//! ```
97//!
98//! If you need to send more than one query in a single request, create a [`Batch`].
99//! All queries in the batch will share the same parameters, such as
100//! keyspace, consistency level or timestamp. Send the batch for execution with
101//! [`StargateClient::execute_batch`].
102//!
103//! ### Processing the result set
104//! A [`ResultSet`] comes back as a collection of rows. A [`Row`] can be easily unpacked
105//! into a tuple:
106//
107//! ```rust
108//! # use std::convert::TryInto;
109//! # use stargate_grpc::ResultSet;
110//! # fn process_results(result_set: ResultSet) -> anyhow::Result<()> {
111//! for row in result_set.rows {
112//!     let (login, emails): (String, Vec<String>) = row.try_into()?;
113//!     // ...
114//! }
115//! # Ok(())
116//! # }
117//! ```
118//!
119//! It is also possible to read each field separately and convert it to desired type:
120//! ```rust
121//! # use std::convert::TryInto;
122//! # use stargate_grpc::ResultSet;
123//! # fn process_results(result_set: ResultSet) -> anyhow::Result<()> {
124//! for mut row in result_set.rows {
125//!     let login: String = row.try_take(0)?;
126//!     let emails: Vec<String> = row.try_take(1)?;
127//!     // ...
128//! }
129//! # Ok(())
130//! # }
131//! ```
132//!
133//! Alternativelly, you can convert a whole `Row` into a struct with a mapper obtained
134//! from [`ResultSet::mapper`](ResultSet::mapper):
135//!
136//! ```
137//! # #[cfg(feature = "macros")]
138//! # {
139//! use stargate_grpc::{ResultSet, TryFromRow};
140//! # fn process_results(result_set: ResultSet) -> anyhow::Result<()> {
141//!
142//! #[derive(TryFromRow)]
143//! struct User {
144//!     login: String,
145//!     emails: Vec<String>
146//! }
147//!
148//! let mapper = result_set.mapper()?;
149//! for row in result_set.rows {
150//!     let user: User = mapper.try_unpack(row)?;
151//!     // ...
152//! }
153//! # Ok(())
154//! # }}
155//! ```
156//!
157//! ## Representation of values
158//!
159//! The values bound in queries and the values received in the `Row`s of a `ResultSet`
160//! are internally represented by `struct` [`Value`]. A `Value` wraps an `enum` that can
161//! hold one of many data types. `Value` provides factory functions that produce values
162//! of desired type, so it is easy to construct them.
163//!
164//! ```rust
165//! use stargate_grpc::Value;
166//!
167//! let bool = Value::boolean(true);
168//! let int = Value::bigint(1);
169//! let double = Value::double(1.0);
170//! let string = Value::string("stargate");
171//! let list = Value::list(vec![Value::bigint(1), Value::bigint(2), Value::bigint(3)]);
172//! let map = Value::map(vec![("key1", Value::bigint(1)), ("key2", Value::bigint(2))]);
173//! ```
174//!
175//! List or maps can hold values of different types:
176//! ```rust
177//! # use stargate_grpc::Value;
178//! let heterogeneous_list = vec![Value::bigint(1), Value::double(3.14)];
179//! ```
180//!
181//! Values can be used in calls to `bind` or `bind_name` used when building queries or batches:
182//!
183//! ```rust
184//! use stargate_grpc::{Query, Value};
185//! let query = Query::builder()
186//!     .query("SELECT login, emails FROM users WHERE id = :id")
187//!     .bind_name("id", Value::bigint(1000))
188//!     .build();
189//! ```
190//!
191//! A [`Row`] is represented by a vector of `Value`s:
192//! ```rust
193//! use std::convert::TryInto;
194//! use stargate_grpc::{Row, Value};
195//!
196//! let row = Row { values: vec![Value::bigint(1), Value::double(3.14)] };
197//! ```
198//!
199//! Values can be converted to and from other commonly used Rust types.
200//! For more examples, refer to the documentation of modules [`from_value`] and [`into_value`].
201//!
202//! ### Working with UUIDs
203//! This crate provides only a very lightweight representation of UUIDs: [`proto::Uuid`].
204//! A UUID is internally represented as an vector of bytes.
205//! That struct does not provide any functions to generate nor manipulate the
206//! UUID value, however, it should be fairly easy to convert to from other UUID representations.
207//!
208//! To get support for conversions from and to
209//! [`uuid::UUID`](https://docs.rs/uuid/0.8/uuid/struct.Uuid.html),
210//! bring [`uuid`](https://crates.io/crates/uuid) on the dependency list and enable feature `uuid`.
211//! ```toml
212//! [dependencies]
213//! uuid = "0.8
214//! stargate-grpc = { version = "0.1", features = ["uuid"] }
215//! ```
216//!
217//! ### Working with times, dates and timestamps
218//! This crate doesn't define its own fully-fledged
219//! structures for representing dates, times and timestamps.
220//! It allows an easy integration with external structures instead.
221//!
222//! A time value is internally represented as an `u64` number of nanoseconds elapsed
223//! since midnight. Hence, `Value::time(0)` denotes midnight.
224//!
225//! A date is internally represented as an `u32` number where value 2^31 denotes Unix Epoch.
226//! For convenience and compatibility with most other date representations, values are convertible
227//! to and from `i32` type where 0 denotes the Unix Epoch. Therefore the Unix Epoch can be simply
228//! written as `Value::date(0)` which is equivalent to `Value::raw_date(1 << 31)`.
229//!
230//! A timestamp is internally represented as an `i64` number of milliseconds
231//! elapsed since Unix epoch. Timestamps can be negative.
232//!
233//! Using integers to represent dates is error-prone, therefore
234//! this library comes with conversions
235//! from higher-level structures like [`SystemTime`](std::time::SystemTime):
236//!
237//! ```rust
238//! use std::time::SystemTime;
239//! use stargate_grpc::Value;
240//!
241//! let unix_epoch_1 = Value::timestamp(SystemTime::UNIX_EPOCH);
242//! let unix_epoch_2 = Value::timestamp(0);
243//! assert_eq!(unix_epoch_1, unix_epoch_2);
244//! ```
245//!
246//! More time related features are provided by an optional `chrono` feature.
247//! If you enable `chrono` feature, you get conversions for
248//! [`chrono::Date`](https://docs.rs/chrono/0.4/chrono/struct.Date.html) and
249//! [`chrono::DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html).
250//!
251//! ```toml
252//! [dependencies]
253//! chrono = "0.4"
254//! stargate-grpc = { version = "0.1", features = ["chrono"] }
255//! ```
256//!
257//! ### Mapping Rust structs to user defined types
258//! Feature
259//! [`stargate-grpc-derive`](/stargate_grpc_derive/)
260//! allows to generate conversions between `Value`s and your Rust structs by adding
261//! the `#[derive(IntoValue, TryFromValue)]` attribute on top of a struct definition.
262//!
263//!
264
265pub use client::{AuthToken, StargateClient};
266pub use from_value::TryFromValue;
267pub use into_value::{DefaultCqlType, IntoValue};
268pub use proto::{Batch, Consistency, Query, ResultSet, Row, Value};
269#[cfg(feature = "stargate-grpc-derive")]
270pub use stargate_grpc_derive::*;
271
272pub mod client;
273pub mod from_value;
274pub mod into_value;
275pub mod query;
276pub mod result;
277
278pub mod error;
279pub mod types;
280
281/// Structures automatically generated from gRPC protocol definition files located in `api/`.
282pub mod proto {
283    tonic::include_proto!("stargate");
284}
285
286/// Holds a key and a value pair; used in map representation.
287///
288/// Maps are passed as collections of key-value pairs, where items (0, 2, 4, ...) are keys,
289/// and items (1, 3, 5, ...) are values. This means key-value pairs are not encoded as nested
290/// collections. Hence, in order to receive a map, we must convert it to `Vec<KeyValue<K, V>>`
291/// and *not* into `Vec<(K, V)>`.
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct KeyValue<K, V>(pub K, pub V);
294
295impl<K, V> KeyValue<K, V> {
296    pub fn into_tuple(self) -> (K, V) {
297        (self.0, self.1)
298    }
299}