cdrs 2.0.0-beta.1

Cassandra DB driver written in Rust
Documentation

CDRS crates.io version Build Status Build status

CDRS is Apache Cassandra driver written in pure Rust. The driver implements all the features described in Cassandra binary protocol specification (versions 3 and 4).

Describing Cassandra Cluster and starting new Session

In order to start any communication with Cassandra cluster that requires authentication there should be provided a list of Cassandra nodes (IP addresses of machines where Cassandra is installed and included into a cluster). To get more details how to configure multinode cluster refer, for instance, to DataStax documentation.

use cdrs::cluster::Cluster;
let cluster = Cluster::new(vec!["youraddress_1:9042", "youraddress_2:9042"], authenticator);

First agrument is a Rust Vec of node addresses, the second argument could be any structure that implements cdrs::authenticators::Authenticator trait. This allows to use custom authentication strategies, but in this case developers should implement authenticators by themselves. Out of the box CDRS provides two types of authenticators:

  • cdrs::authenticators::NoneAuthenticator that should be used if authentication is disabled (Cassandra authenticator is set to AllowAllAuthenticator) on server.

  • cdrs::authenticators::PasswordAuthenticator that should be used if authentication is enabled on the server and authenticator is PasswordAuthenticator:

use cdrs::authenticators::PasswordAuthenticator;
let authenticator = PasswordAuthenticator::new("user", "pass");

When cluster nodes are described new Session could be established. Each new Session has its own load balancing strategy as well as data compression. (CDRS supports both Snappy and LZ4 compresssions)

let mut no_compression = cluster.connect(RoundRobin::new())
                                  .expect("No compression connection error");
  let mut lz4_compression = cluster.connect_lz4(RoundRobin::new())
                                   .expect("LZ4 compression connection error");
  let mut snappy_compression = cluster.connect_snappy(RoundRobin::new())
                                      .expect("Snappy compression connection error");

where the first argument of each connect methods is a load balancer. Each structure that implements cdrs::load_balancing::LoadBalancingStrategy could be used as a load balancer during establishing new Session. CDRS provides two strategies out of the box: cdrs::load_balancing::{RoundRobin, Random}. Having been set once at the start load balancing strategy cannot be changed during the session.

Unlike to load balancing compression method could be changed without session restart:

use compression::Compression;
let mut session = cluster.connect(RoundRobin::new())
                                  .expect("No compression connection error");
session.compression = Compression::LZ4;

Starting new SSL-encrypted Session

SSL-encrypted connection is also awailable with CDRS however to get this working CDRS itself should be imported with ssl feature enabled:

[dependencies]
openssl = "0.9.6"

[dependencies.cdrs]
version = "*"
features = ["ssl"]

Another difference comparing to non-encrypted connection is necessity to create SSLConnector

use std::path::Path;
use openssl::ssl::{SslConnectorBuilder, SslMethod};
use cdrs::client::CDRS;
use cdrs::authenticators::PasswordAuthenticator;
use cdrs::transport::TransportTls;

// here needs to be a path to your SSL certificate
let path = Path::new("./node0.cer.pem");
let mut ssl_connector_builder = SslConnectorBuilder::new(SslMethod::tls()).unwrap();
ssl_connector_builder.builder_mut().set_ca_file(path).unwrap();
let connector = ssl_connector_builder.build();

When these preparation are done we're good to start SSL-encrypted session.

let mut no_compression = cluster.connect_ssl(RoundRobin::new())
                                  .expect("No compression connection error");
  let mut lz4_compression = cluster.connect_lz4_ssl(RoundRobin::new())
                                   .expect("LZ4 compression connection error");
  let mut snappy_compression = cluster.connect_snappy_ssl(RoundRobin::new())
                                      .expect("Snappy compression connection error");

More details regarding configuration Cassandra server for SSL-encrypted Client-Node communication could be found, for instance, on DataStax website.

Executing queries

CDRS Session implements cdrs::query::QueryExecutor trait that provides few options for immediate query execution:

// simple query
session.query("SELECT * from my.store").unwrap();

// simple query with tracing and warnings
let with_tracing = true;
let with_warnings = true;
session.query_tw("SELECT * FROM my.store", with_tracing, with_warnings).unwrap();

// query with query values
let values = query_values!(1 as i32, 1 as i64);
session.query_tw("INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)", values).unwrap();

// query with query values, tracing and warnings
let with_tracing = true;
let with_warnings = true;
let values = query_values!(1 as i32, 1 as i64);
session.query_tw("INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)", values, with_tracing, with_warnings).unwrap();

// query with query params
use cdrs::query::QueryParamsBuilder;
use cdrs::consistency::Consistency;

let mut params = QueryParamsBuilder::new();
params = params.consistency(Consistency::Any);
session.query_with_params("SELECT * FROM my.store", params.finalize()).unwrap();

// query with query params and tracing, warnings
use cdrs::query::QueryParamsBuilder;
use cdrs::consistency::Consistency;

let with_tracing = true;
let with_warnings = true;

let mut params = QueryParamsBuilder::new();
params = params.consistency(Consistency::Any);

session.query_with_params_tw("SELECT * FROM my.store", params.finalize(), with_tracing, with_warnings).unwrap();

Preparing queries

During preparing a query a server parses the query, saves parsing result into cache and returns back to a client an ID that could be further used for executing prepared statement with different parameters (such as values, consistency etc.). When a server executes prepared query it doesn't need to parse it so parsing step will be skipped.

CDRS Session implements cdrs::query::PrepareExecutor trait that provides few option for preparing query:

let prepared_query = session.prepare("INSERT INTO my.store (my_int, my_bigint) VALUES (?, ?)").unwrap();

// or with tracing and warnings
let with_tracing = true;
let with_warnings = true;

let prepred_query = session.prepare_tw("INSERT INTO my.store (my_int, my_bigint) VALUES (?, ?)", with_tracing, with_warnings).unwrap();

Executing prepared queries

When query is prepared on the server client gets prepared query id of type cdrs::query::PreparedQuery. Having such id it's possible to execute prepared query using session methods from cdrs::query::ExecExecutor:

// execute prepared query without specifying any extra parameters or values
session.exec(&preparedQuery).unwrap();

// execute prepared query with tracing and warning information
let with_tracing = true;
let with_warnings = true;

session.exec_tw(&preparedQuery, with_tracing, with_warnings).unwrap();

// execute prepared query with values
let values_with_names = query_values!{"my_bigint" => bigint, "my_int" => int};

session.exec_with_values(&preparedQuery, values_with_names).unwrap();

// execute prepared query with values with warnings and tracing information
let with_tracing = true;
let with_warnings = true;

let values_with_names = query_values!{"my_bigint" => 1 as i64, "my_int" => 2 as i32};

session.exec_with_values_tw(&preparedQuery, values_with_names, with_tracing, with_warnings).unwrap();

// execute prepared query with parameters
use cdrs::query::QueryParamsBuilder;
use cdrs::consistency::Consistency;

let mut params = QueryParamsBuilder::new();
params = params.consistency(Consistency::Any);
session.exec_with_parameters(&preparedQuery, params.finalize()).unwrap();

// execute prepared query with parameters, tracing and warning information
use cdrs::query::QueryParamsBuilder;
use cdrs::consistency::Consistency;

let with_tracing = true;
let with_warnings = true;
let mut params = QueryParamsBuilder::new();
params = params.consistency(Consistency::Any);
session.exec_with_parameters_tw(&preparedQuery, params.finalize(), with_tracing, with_warnings).unwrap();

Batch queries

CDRS Session supports batching few queries in a single request to Apache Cassandra via implementing cdrs::query::BatchExecutor trait:

// batch two queries
use cdrs::query::{BatchQueryBuilder, QueryBatch};

let mut queries = BatchQueryBuilder::new();
queries = queries.add_query_prepared(&prepared_query);
queries = queries.add_query("INSERT INTO my.store (my_int) VALUES (?)", query_values!(1 as i32));
session.batch_with_params(queries.finalyze());

// batch queries with tracing and warning information
use cdrs::query::{BatchQueryBuilder, QueryBatch};

let with_tracing = true;
let with_warnings = true;
let mut queries = BatchQueryBuilder::new();
queries = queries.add_query_prepared(&prepared_query);
queries = queries.add_query("INSERT INTO my.store (my_int) VALUES (?)", query_values!(1 as i32));
session.batch_with_params_tw(queries.finalyze(), with_tracing, with_warnings);

Query values types

Accordingly to specification along with queries there could be provided something that is called values. Apache Cassandra server will use values instead of ? symbols from a query string.

There are two types of queries defined in the spec and supported by CDRS driver. Each of these two types could be easily constructed via provided query_values! macros.

  • simple values - could be imagine as a list of values. The order of simple values matters because server will put them in the same number as columns were provided in query string.
let simple_values = query_values!(1 as i32, 2 as i32);
  • named values are similar to hash maps, where keys represent column names which the a value has to be assigned to.
let values_with_names = query_values!{"my_bigint" => 1 as i64, "my_int" => 2 as i32};

Each type that implements Into<cdrs::types::value::Value> could be used as a value in query_values! macros. For primitive types please refer to following wrapper CDRS types that could be easily converted to Value. For custom types (in Cassandra terminology User Defined Types) IntoCDRSValue derive could be used:

#[derive(Debug, IntoCDRSValue)]
struct Udt {
    pub number: i32,
    pub number_16: i16,
    pub number_8: N,
}

// for nested structures it works as well
#[derive(Debug, IntoCDRSValue)]
struct N {
    pub n: i16,
}

Look into this link to find a full example how to use CDRS + into-cdrs-value-derive crate.

Mapping results into Rust structures

In ordert to query information from Cassandra DB and transform results to Rust types an structures each row in a query result should be transformed leveraging one of following traits provided by CDRS cdrs::types::{AsRustType, AsRust, IntoRustByName, ByName, IntoRustByIndex, ByIndex}.

  • AsRustType may be used in order to transform such complex structures as Cassandra lists, sets, tuples. The Cassandra value in this case could non-set and null values.

  • AsRust trait may be used for similar purposes as AsRustType but it assumes that Cassandra value is neither non-set nor null value. Otherwise it panics.

  • IntoRustByName trait may be used to access a value as a Rust structure/type by name. Such as in case of rows where each column has its own name, and maps. These values may be as well non-set and null.

  • ByName trait is the same as IntoRustByName but value should be neither non-set nor null. Otherwise it panics.

  • IntoRustByIndex is the same as IntoRustByName but values could be accessed via column index basing on their order provided in query. These values may be as well non-set and null.

  • ByIndex is the same as IntoRustByIndex but value can be neither non-set nor null. Otherwise it panics.

Relations between Cassandra and Rust types are described in type-mapping.md. For details see examples.