├── rustfmt.toml ├── .gitignore ├── cdrs-logo.png ├── perf-mixed.png ├── perf-inserts.png ├── perf-selects.png ├── .github ├── FUNDING.yml ├── dependabot.yml ├── stale.yml └── workflows │ └── rust.yml ├── cdrs-tokio ├── src │ ├── retry.rs │ ├── statement.rs │ ├── future.rs │ ├── cluster │ │ ├── topology │ │ │ ├── datacenter_metadata.rs │ │ │ ├── keyspace_metadata.rs │ │ │ ├── replication_strategy.rs │ │ │ ├── node_distance.rs │ │ │ └── node_state.rs │ │ ├── session_context.rs │ │ ├── node_info.rs │ │ ├── topology.rs │ │ ├── config_proxy.rs │ │ ├── keyspace_holder.rs │ │ ├── node_address.rs │ │ ├── send_envelope.rs │ │ ├── config_tcp.rs │ │ └── config_rustls.rs │ ├── load_balancing │ │ ├── request.rs │ │ ├── random.rs │ │ ├── initializing_wrapper.rs │ │ ├── round_robin.rs │ │ └── node_distance_evaluator.rs │ ├── macros.rs │ ├── load_balancing.rs │ ├── statement │ │ └── statement_params.rs │ ├── speculative_execution.rs │ ├── frame_encoding.rs │ ├── lib.rs │ ├── cluster.rs │ └── envelope_parser.rs ├── examples │ ├── README.md │ ├── prepare_batch_execute.rs │ └── multiple_thread.rs ├── tests │ ├── multithread.rs │ ├── query_values.rs │ ├── paged_query.rs │ ├── compression.rs │ ├── common.rs │ ├── single_node_speculative_execution.rs │ ├── multi_node_speculative_execution.rs │ └── topology_aware.rs └── Cargo.toml ├── clippy.toml ├── cassandra-protocol ├── src │ ├── query │ │ ├── utils.rs │ │ ├── prepare_flags.rs │ │ ├── prepared_query.rs │ │ ├── query_flags.rs │ │ ├── query_values.rs │ │ ├── query_params_builder.rs │ │ └── batch_query_builder.rs │ ├── lib.rs │ ├── events.rs │ ├── query.rs │ ├── crc.rs │ ├── types │ │ ├── blob.rs │ │ ├── list.rs │ │ ├── vector.rs │ │ ├── udt.rs │ │ ├── tuple.rs │ │ ├── duration.rs │ │ ├── from_cdrs.rs │ │ ├── decimal.rs │ │ └── rows.rs │ ├── frame │ │ ├── message_ready.rs │ │ ├── message_options.rs │ │ ├── message_authenticate.rs │ │ ├── message_auth_challenge.rs │ │ ├── message_auth_success.rs │ │ ├── message_event.rs │ │ ├── message_auth_response.rs │ │ ├── message_register.rs │ │ ├── message_supported.rs │ │ ├── traits.rs │ │ ├── message_request.rs │ │ ├── message_query.rs │ │ ├── message_execute.rs │ │ ├── message_startup.rs │ │ └── message_prepare.rs │ ├── token.rs │ └── authenticators.rs ├── README.md └── Cargo.toml ├── cassandra-ports.txt ├── Cargo.toml ├── documentation ├── README.md ├── batching-multiple-queries.md ├── type-mapping.md ├── preparing-and-executing-queries.md ├── deserialization.md ├── cdrs-session.md ├── cluster-configuration.md └── query-values.md ├── cdrs-tokio-helpers-derive ├── Cargo.toml ├── README.md └── src │ ├── try_from_row.rs │ ├── try_from_udt.rs │ ├── db_mirror.rs │ ├── lib.rs │ └── into_cdrs_value.rs ├── LICENSE-MIT └── README.md /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_field_init_shorthand = true 2 | edition = "2018" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | *.bk 4 | .idea/ 5 | cdrs.iml 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /cdrs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krojew/cdrs-tokio/HEAD/cdrs-logo.png -------------------------------------------------------------------------------- /perf-mixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krojew/cdrs-tokio/HEAD/perf-mixed.png -------------------------------------------------------------------------------- /perf-inserts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krojew/cdrs-tokio/HEAD/perf-inserts.png -------------------------------------------------------------------------------- /perf-selects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krojew/cdrs-tokio/HEAD/perf-selects.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: krojew 4 | -------------------------------------------------------------------------------- /cdrs-tokio/src/retry.rs: -------------------------------------------------------------------------------- 1 | mod reconnection_policy; 2 | mod retry_policy; 3 | 4 | pub use reconnection_policy::*; 5 | pub use retry_policy::*; 6 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # the main Error enum is a bit above the default limit, but contains very useful data for pattern matching 2 | large-error-threshold = 256 3 | -------------------------------------------------------------------------------- /cdrs-tokio/src/statement.rs: -------------------------------------------------------------------------------- 1 | mod statement_params; 2 | mod statement_params_builder; 3 | 4 | pub use statement_params::*; 5 | pub use statement_params_builder::*; 6 | -------------------------------------------------------------------------------- /cdrs-tokio/src/future.rs: -------------------------------------------------------------------------------- 1 | /// An owned dynamically typed `Future` for use in cases where you can't 2 | /// statically type your result or need to add some indirection. 3 | pub type BoxFuture<'a, T> = futures::future::BoxFuture<'a, T>; 4 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/utils.rs: -------------------------------------------------------------------------------- 1 | /// Returns the identifier in a format appropriate for concatenation in a CQL query. 2 | #[inline] 3 | pub fn quote(text: &str) -> String { 4 | format!("\"{}\"", text.replace('"', "\"\"")) 5 | } 6 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology/datacenter_metadata.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | 3 | /// Information about a datacenter. 4 | #[derive(Clone, Debug, Constructor)] 5 | pub struct DatacenterMetadata { 6 | pub rack_count: usize, 7 | } 8 | -------------------------------------------------------------------------------- /cassandra-ports.txt: -------------------------------------------------------------------------------- 1 | Cassandra ports: 2 | 3 | * 7199 - JMX (was 8080 pre Cassandra 0.8.xx) 4 | * 7000 - Internode communication (not used if TLS enabled) 5 | * 7001 - TLS Internode communication (used if TLS enabled) 6 | * 9160 - Thrift client API 7 | * 9042 - CQL native transport port 8 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology/keyspace_metadata.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | 3 | use crate::cluster::topology::ReplicationStrategy; 4 | 5 | /// Keyspace metadata. 6 | #[derive(Clone, Debug, Constructor)] 7 | pub struct KeyspaceMetadata { 8 | pub replication_strategy: ReplicationStrategy, 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "cassandra-protocol", 4 | "cdrs-tokio", 5 | "cdrs-tokio-helpers-derive" 6 | ] 7 | 8 | [workspace.dependencies] 9 | arc-swap = "1.7.1" 10 | uuid = "1.19.0" 11 | derivative = "2.2.0" 12 | derive_more = { version = "2.1.0", features = ["constructor", "display"] } 13 | itertools = "0.14.0" 14 | thiserror = "2.0.17" 15 | -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | # Definitive guide 2 | 3 | - [Cluster configuration](./cluster-configuration.md). 4 | - [CDRS session](./cdrs-session.md): 5 | - [Query values](./query-values.md) 6 | - [Cassandra-to-Rust deserialization](./deserialization.md). 7 | - [Preparing and executing queries](./preparing-and-executing-queries.md). 8 | - [Batching multiple queries](./batching-multiple-queries.md). 9 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology/replication_strategy.rs: -------------------------------------------------------------------------------- 1 | use fxhash::FxHashMap; 2 | 3 | /// A replication strategy determines the nodes where replicas are placed. 4 | #[derive(Debug, Clone)] 5 | pub enum ReplicationStrategy { 6 | SimpleStrategy { 7 | replication_factor: usize, 8 | }, 9 | NetworkTopologyStrategy { 10 | datacenter_replication_factor: FxHashMap, 11 | }, 12 | Other, 13 | } 14 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/session_context.rs: -------------------------------------------------------------------------------- 1 | use crate::transport::CdrsTransport; 2 | use arc_swap::ArcSwapOption; 3 | 4 | pub struct SessionContext { 5 | pub control_connection_transport: ArcSwapOption, 6 | } 7 | 8 | impl Default for SessionContext { 9 | fn default() -> Self { 10 | SessionContext { 11 | control_connection_transport: ArcSwapOption::empty(), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing/request.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::consistency::Consistency; 2 | use derive_more::Constructor; 3 | 4 | use crate::cluster::Murmur3Token; 5 | 6 | /// A request executed by a `Session`. 7 | #[derive(Constructor, Clone, Debug)] 8 | pub struct Request<'a> { 9 | pub keyspace: Option<&'a str>, 10 | pub token: Option, 11 | pub routing_key: Option<&'a [u8]>, 12 | pub consistency: Option, 13 | } 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /cassandra-protocol/README.md: -------------------------------------------------------------------------------- 1 | # Cassandra protocol [![crates.io version](https://img.shields.io/crates/v/cassandra-protocol.svg)](https://crates.io/crates/cassandra-protocol) ![build status](https://github.com/krojew/cdrs-tokio/actions/workflows/rust.yml/badge.svg) 2 | 3 | **Cassandra** low-level protocol implementation, written in Rust. 4 | 5 | If you wish to use **Cassandra** without dealing with protocol-level details, consider a high-level crate 6 | like **[cdrs-tokio](https://crates.io/crates/cdrs-tokio)**. 7 | -------------------------------------------------------------------------------- /cassandra-protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A generic cassandra protocol crate. 2 | //! Built in coordination with cdrs-tokio but is flexible for many usecases. 3 | 4 | extern crate core; 5 | 6 | #[macro_use] 7 | mod macros; 8 | 9 | pub mod frame; 10 | pub mod query; 11 | pub mod types; 12 | 13 | pub mod authenticators; 14 | pub mod compression; 15 | pub mod consistency; 16 | pub mod crc; 17 | pub mod error; 18 | pub mod events; 19 | pub mod token; 20 | 21 | pub type Error = error::Error; 22 | pub type Result = error::Result; 23 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cdrs-tokio-helpers-derive" 3 | version = "5.0.3" 4 | authors = ["Alex Pikalov ", "Kamil Rojewski "] 5 | description = "Derive CDRS helper traits" 6 | license = "MIT/Apache-2.0" 7 | repository = "https://github.com/krojew/cdrs-tokio" 8 | edition = "2018" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | itertools = "0.14.0" 15 | proc-macro2 = "1.0.103" 16 | syn = "2.0.111" 17 | quote = "1.0.42" 18 | 19 | -------------------------------------------------------------------------------- /documentation/batching-multiple-queries.md: -------------------------------------------------------------------------------- 1 | ### Batch queries 2 | 3 | CDRS `Session` supports batching few queries in a single request to Apache Cassandra: 4 | 5 | ```rust 6 | // batch two queries 7 | use cdrs_tokio::query::{BatchQueryBuilder, QueryBatch}; 8 | 9 | let mut queries = BatchQueryBuilder::new(); 10 | queries = queries.add_query_prepared(&prepared_query); 11 | queries = queries.add_query("INSERT INTO my.store (my_int) VALUES (?)", query_values!(1 as i32)); 12 | session.batch_with_params(queries.finalyze()).await; 13 | ``` 14 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/node_info.rs: -------------------------------------------------------------------------------- 1 | use derivative::Derivative; 2 | use derive_more::Constructor; 3 | use std::net::SocketAddr; 4 | use uuid::Uuid; 5 | 6 | use crate::cluster::Murmur3Token; 7 | 8 | /// Information about a node. 9 | #[derive(Constructor, Clone, Derivative)] 10 | #[derivative(Debug)] 11 | pub struct NodeInfo { 12 | pub host_id: Uuid, 13 | pub broadcast_rpc_address: SocketAddr, 14 | pub broadcast_address: Option, 15 | pub datacenter: String, 16 | #[derivative(Debug = "ignore")] 17 | pub tokens: Vec, 18 | pub rack: String, 19 | } 20 | -------------------------------------------------------------------------------- /cassandra-protocol/src/events.rs: -------------------------------------------------------------------------------- 1 | use crate::frame::events::{ 2 | SchemaChange as MessageSchemaChange, ServerEvent as MessageServerEvent, 3 | SimpleServerEvent as MessageSimpleServerEvent, 4 | }; 5 | 6 | /// Full Server Event which includes all details about occurred change. 7 | pub type ServerEvent = MessageServerEvent; 8 | 9 | /// Simplified Server event. It should be used to represent an event 10 | /// which consumer wants listen to. 11 | pub type SimpleServerEvent = MessageSimpleServerEvent; 12 | 13 | /// Reexport of `MessageSchemaChange`. 14 | pub type SchemaChange = MessageSchemaChange; 15 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query.rs: -------------------------------------------------------------------------------- 1 | pub mod batch_query_builder; 2 | pub mod prepare_flags; 3 | pub mod prepared_query; 4 | pub mod query_flags; 5 | pub mod query_params; 6 | pub mod query_params_builder; 7 | pub mod query_values; 8 | pub mod utils; 9 | 10 | pub use crate::query::batch_query_builder::{BatchQueryBuilder, QueryBatch}; 11 | pub use crate::query::prepare_flags::PrepareFlags; 12 | pub use crate::query::prepared_query::PreparedQuery; 13 | pub use crate::query::query_flags::QueryFlags; 14 | pub use crate::query::query_params::QueryParams; 15 | pub use crate::query::query_params_builder::QueryParamsBuilder; 16 | pub use crate::query::query_values::QueryValues; 17 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use fxhash::FxHashMap; 4 | use uuid::Uuid; 5 | 6 | pub mod cluster_metadata; 7 | mod datacenter_metadata; 8 | mod keyspace_metadata; 9 | mod node; 10 | mod node_distance; 11 | mod node_state; 12 | mod replication_strategy; 13 | 14 | pub use self::datacenter_metadata::DatacenterMetadata; 15 | pub use self::keyspace_metadata::KeyspaceMetadata; 16 | pub use self::node::Node; 17 | pub use self::node_distance::NodeDistance; 18 | pub use self::node_state::NodeState; 19 | pub use self::replication_strategy::ReplicationStrategy; 20 | 21 | /// Map from host id to a node. 22 | pub type NodeMap = FxHashMap>>; 23 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/README.md: -------------------------------------------------------------------------------- 1 | # cdrs-tokio-helpers-derive 2 | 3 | Procedural macros that derive helper traits for CDRS Cassandra to Rust types conversion back and forth 4 | 5 | Features: 6 | 7 | * convert Cassandra primitive types (not lists, sets, maps, UDTs) into Rust 8 | * recursively convert Cassandra "collection" types (lists, sets, maps) into Rust 9 | * recursively convert Cassandra UDTs into Rust 10 | * recursively convert optional fields into Rust 11 | * convert Rust primitive types into Cassandra query values 12 | * convert Rust "collection" types into Cassandra query values 13 | * convert Rust structures into Cassandra query values 14 | * convert `Option` into Cassandra query value 15 | * generates an insert method for a Rust struct type 16 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /cdrs-tokio/examples/README.md: -------------------------------------------------------------------------------- 1 | # CDRS examples 2 | 3 | - [`crud_operations.rs`](./crud_operations.rs) demonstrates how to create keyspace, table and user defined type. As well basic CRUD (Create, Read, Update, Delete) operations are shown; 4 | - [`insert_collection.rs`](./insert_collection.rs) demonstrates how to insert items in lists, maps and sets; 5 | - [`multiple_thread.rs`](./multiple_thread.rs) shows how to use CDRS in multi thread applications; 6 | - [`paged_query.rs`](./paged_query.rs) uncovers query paging; 7 | - [`prepare_batch_execute.rs`](./prepare_batch_execute.rs) provides an example of query preparation and batching; 8 | - [`aws cassandra crud operations`](https://github.com/AERC18/cdrs-aws-cassandra) illustrates how to connect and do CRUD operations on Amazon Managed Apache Cassandra Service. 9 | -------------------------------------------------------------------------------- /cassandra-protocol/src/crc.rs: -------------------------------------------------------------------------------- 1 | use crc32fast::Hasher; 2 | 3 | const CRC24_POLY: i32 = 0x1974f0b; 4 | const CRC24_INIT: i32 = 0x875060; 5 | 6 | /// Computes crc24 value of `bytes`. 7 | pub fn crc24(bytes: &[u8]) -> i32 { 8 | bytes.iter().fold(CRC24_INIT, |mut crc, byte| { 9 | crc ^= (*byte as i32) << 16; 10 | 11 | for _ in 0..8 { 12 | crc <<= 1; 13 | if (crc & 0x1000000) != 0 { 14 | crc ^= CRC24_POLY; 15 | } 16 | } 17 | 18 | crc 19 | }) 20 | } 21 | 22 | /// Computes crc32 value of `bytes`. 23 | pub fn crc32(bytes: &[u8]) -> u32 { 24 | let mut hasher = Hasher::new(); 25 | hasher.update(&[0xfa, 0x2d, 0x55, 0xca]); // Cassandra appends a few bytes and forgets to mention it in the spec... 26 | hasher.update(bytes); 27 | hasher.finalize() 28 | } 29 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology/node_distance.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | 3 | /// Determines how the driver will manage connections to a Cassandra node. 4 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display)] 5 | pub enum NodeDistance { 6 | /// An "active" distance that, indicates that the driver should maintain connections to the 7 | /// node; it also marks it as "preferred", meaning that the node may have priority for 8 | /// some tasks (for example, being chosen as the control connection host). 9 | Local, 10 | /// An "active" distance that, indicates that the driver should maintain connections to the 11 | /// node; it also marks it as "less preferred", meaning that other nodes may have a higher 12 | /// priority for some tasks (for example, being chosen as the control connection host). 13 | Remote, 14 | } 15 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/blob.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | 3 | /// Special type that represents Cassandra blob type. 4 | #[derive(PartialEq, Eq, Hash, Debug, Clone, Constructor)] 5 | #[repr(transparent)] 6 | pub struct Blob(Vec); 7 | 8 | impl Blob { 9 | /// Returns a mutable reference to an underlying slice of bytes. 10 | #[inline] 11 | pub fn as_mut_slice(&mut self) -> &[u8] { 12 | self.0.as_mut_slice() 13 | } 14 | 15 | /// Returns underlying vector of bytes. 16 | #[inline] 17 | pub fn into_vec(self) -> Vec { 18 | self.0 19 | } 20 | } 21 | 22 | impl From> for Blob { 23 | #[inline] 24 | fn from(vec: Vec) -> Self { 25 | Blob::new(vec) 26 | } 27 | } 28 | 29 | impl From<&[u8]> for Blob { 30 | #[inline] 31 | fn from(value: &[u8]) -> Self { 32 | Blob::new(value.to_vec()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /documentation/type-mapping.md: -------------------------------------------------------------------------------- 1 | ### Type relations between Rust (in CDRS approach) and Apache Cassandra 2 | 3 | #### primitive types (`T`) 4 | 5 | | Cassandra | Rust | Feature 6 | |-----------|-------|-------| 7 | | tinyint | i8 | v4, v5 | 8 | | smallint | i16 | v4, v5 | 9 | | int | i32 | all | 10 | | bigint | i64 | all | 11 | | ascii | String | all | 12 | | text | String | all | 13 | | varchar | String | all | 14 | | boolean | bool | all | 15 | | time | i64 | all | 16 | | timestamp | i64 | all | 17 | | float | f32 | all | 18 | | double | f64 | all | 19 | | uuid | [Uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) | all | 20 | | counter | i64 | all | 21 | 22 | #### complex types 23 | | Cassandra | Rust + CDRS | 24 | |-----------|-------------| 25 | | blob | `Blob -> Vec<>` | 26 | | list | `List -> Vec` | 27 | | set | `List -> Vec` | 28 | | map | `Map -> HashMap` | 29 | | udt | Rust struct | 30 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/src/try_from_row.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::*; 3 | use syn::{DeriveInput, Result}; 4 | 5 | use crate::common::get_struct_fields; 6 | 7 | pub fn impl_try_from_row(ast: &DeriveInput) -> Result { 8 | let name = &ast.ident; 9 | let fields = get_struct_fields(ast)?; 10 | 11 | Ok(quote! { 12 | #[automatically_derived] 13 | impl cdrs_tokio::frame::TryFromRow for #name { 14 | fn try_from_row(cdrs: cdrs_tokio::types::rows::Row) -> cdrs_tokio::Result { 15 | use cdrs_tokio::frame::TryFromUdt; 16 | use cdrs_tokio::types::from_cdrs::FromCdrsByName; 17 | use cdrs_tokio::types::IntoRustByName; 18 | use cdrs_tokio::types::AsRustType; 19 | 20 | Ok(#name { 21 | #(#fields),* 22 | }) 23 | } 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/src/try_from_udt.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::*; 3 | use syn::{DeriveInput, Result}; 4 | 5 | use crate::common::get_struct_fields; 6 | 7 | pub fn impl_try_from_udt(ast: &DeriveInput) -> Result { 8 | let name = &ast.ident; 9 | let fields = get_struct_fields(ast)?; 10 | 11 | Ok(quote! { 12 | #[automatically_derived] 13 | impl cdrs_tokio::frame::TryFromUdt for #name { 14 | fn try_from_udt(cdrs: cdrs_tokio::types::udt::Udt) -> cdrs_tokio::Result { 15 | use cdrs_tokio::frame::TryFromUdt; 16 | use cdrs_tokio::types::from_cdrs::FromCdrsByName; 17 | use cdrs_tokio::types::IntoRustByName; 18 | use cdrs_tokio::types::AsRustType; 19 | 20 | Ok(#name { 21 | #(#fields),* 22 | }) 23 | } 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /cdrs-tokio/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | 3 | /// Transforms arguments to values consumed by queries. 4 | macro_rules! query_values { 5 | ($($value:expr),*) => { 6 | { 7 | use cdrs_tokio::types::value::Value; 8 | use cdrs_tokio::query::QueryValues; 9 | let mut values: Vec = Vec::new(); 10 | $( 11 | values.push($value.into()); 12 | )* 13 | QueryValues::SimpleValues(values) 14 | } 15 | }; 16 | ($($name:expr => $value:expr),*) => { 17 | { 18 | use cdrs_tokio::types::value::Value; 19 | use cdrs_tokio::query::QueryValues; 20 | use std::collections::HashMap; 21 | let mut values: HashMap = HashMap::new(); 22 | $( 23 | values.insert($name.to_string(), $value.into()); 24 | )* 25 | QueryValues::NamedValues(values) 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_ready.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{FromCursor, Serialize, Version}; 3 | use std::io::Cursor; 4 | 5 | #[derive(Clone, Debug, PartialEq, Default, Ord, PartialOrd, Eq, Hash, Copy)] 6 | pub struct BodyResReady; 7 | 8 | impl Serialize for BodyResReady { 9 | #[inline(always)] 10 | fn serialize(&self, _cursor: &mut Cursor<&mut Vec>, _version: Version) {} 11 | } 12 | 13 | impl FromCursor for BodyResReady { 14 | #[inline(always)] 15 | fn from_cursor(_cursor: &mut Cursor<&[u8]>, _version: Version) -> error::Result { 16 | Ok(BodyResReady) 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn body_res_ready_new() { 26 | let body: BodyResReady = Default::default(); 27 | assert_eq!(body, BodyResReady); 28 | } 29 | 30 | #[test] 31 | fn body_res_ready_serialize() { 32 | let body = BodyResReady; 33 | assert!(body.serialize_to_vec(Version::V4).is_empty()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /documentation/preparing-and-executing-queries.md: -------------------------------------------------------------------------------- 1 | ### Preparing queries 2 | 3 | During preparing a query a server parses the query, saves parsing result into cache and returns 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. 4 | 5 | ```rust 6 | let prepared_query = session.prepare("INSERT INTO my.store (my_int, my_bigint) VALUES (?, ?)").await.unwrap(); 7 | ``` 8 | 9 | ### Executing prepared queries 10 | 11 | When query is prepared on the server client gets prepared query id of type `cdrs_tokio::query::PreparedQuery`. Having such id it's possible to execute prepared query using session methods: 12 | 13 | ```rust 14 | // execute prepared query without specifying any extra parameters or values 15 | session.exec(&preparedQuery).await.unwrap(); 16 | 17 | // to execute prepared query with bound values, use exec_with_values() 18 | // to execute prepared query with advanced parameters, use exec_with_params() 19 | ``` 20 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/prepare_flags.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use std::io::{Cursor, Read}; 3 | 4 | use crate::error::Result; 5 | use crate::frame::{FromCursor, Serialize, Version}; 6 | use crate::types::INT_LEN; 7 | 8 | bitflags! { 9 | pub struct PrepareFlags: u32 { 10 | /// The prepare request contains explicit keyspace. 11 | const WITH_KEYSPACE = 0x01; 12 | } 13 | } 14 | 15 | impl Default for PrepareFlags { 16 | #[inline] 17 | fn default() -> Self { 18 | PrepareFlags::empty() 19 | } 20 | } 21 | 22 | impl Serialize for PrepareFlags { 23 | #[inline] 24 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 25 | self.bits().serialize(cursor, version); 26 | } 27 | } 28 | 29 | impl FromCursor for PrepareFlags { 30 | fn from_cursor(cursor: &mut Cursor<&[u8]>, _version: Version) -> Result { 31 | let mut buff = [0; INT_LEN]; 32 | cursor 33 | .read_exact(&mut buff) 34 | .map(|()| PrepareFlags::from_bits_truncate(u32::from_be_bytes(buff))) 35 | .map_err(|error| error.into()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: Test Suite 11 | runs-on: ubuntu-latest 12 | services: 13 | cassandra: 14 | image: cassandra 15 | ports: 16 | - 9042:9042 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install minimal toolchain with clippy and rustfmt 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | components: rustfmt, clippy 25 | - name: Run tests 26 | # test threads must be one because else database tests will run in parallel and will result in flaky tests 27 | run: cargo test --all-features --verbose -- --test-threads=1 28 | - name: Format check 29 | run: cargo fmt --all -- --check 30 | # Ensure that all targets compile and pass clippy checks under every possible combination of features 31 | - name: Clippy check 32 | run: cargo install cargo-hack && cargo hack --feature-powerset clippy --locked --release 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CDRS Project Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 17 | A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /cassandra-protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cassandra-protocol" 3 | version = "4.0.0" 4 | authors = ["Alex Pikalov ", "Kamil Rojewski "] 5 | edition = "2018" 6 | description = "Cassandra protocol implementation" 7 | documentation = "https://docs.rs/cassandra-protocol" 8 | homepage = "https://github.com/krojew/cdrs-tokio" 9 | repository = "https://github.com/krojew/cdrs-tokio" 10 | keywords = ["cassandra", "client", "cassandradb"] 11 | license = "MIT/Apache-2.0" 12 | categories = ["asynchronous", "database"] 13 | rust-version = "1.74" 14 | 15 | [features] 16 | e2e-tests = [] 17 | 18 | [dependencies] 19 | arc-swap.workspace = true 20 | bitflags = "2.10.0" 21 | bytes = "1.11.0" 22 | chrono = { version = "0.4.31", default-features = false, features = ["std"] } 23 | crc32fast = "1.5.0" 24 | derivative.workspace = true 25 | derive_more.workspace = true 26 | float_eq = "1.0.1" 27 | integer-encoding = "4.1.0" 28 | itertools.workspace = true 29 | num-bigint = "0.4.1" 30 | lz4_flex = "0.12.0" 31 | snap = "1.1.0" 32 | thiserror.workspace = true 33 | time = { version = "0.3.29", features = ["macros"] } 34 | uuid.workspace = true 35 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/config_proxy.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] 2 | pub(crate) struct HttpBasicAuth { 3 | pub(crate) username: String, 4 | pub(crate) password: String, 5 | } 6 | 7 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] 8 | pub struct HttpProxyConfig { 9 | pub(crate) address: String, 10 | pub(crate) basic_auth: Option, 11 | } 12 | 13 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] 14 | pub struct HttpProxyConfigBuilder { 15 | address: String, 16 | basic_auth: Option, 17 | } 18 | 19 | impl HttpProxyConfigBuilder { 20 | /// Creates a new proxy configuration builder with given proxy address. 21 | pub fn new(address: String) -> Self { 22 | Self { 23 | address, 24 | basic_auth: None, 25 | } 26 | } 27 | 28 | /// Adds HTTP basic Auth. 29 | pub fn with_basic_auth(mut self, username: String, password: String) -> Self { 30 | self.basic_auth = Some(HttpBasicAuth { password, username }); 31 | self 32 | } 33 | 34 | /// Build the resulting configuration. 35 | pub fn build(self) -> HttpProxyConfig { 36 | HttpProxyConfig { 37 | basic_auth: self.basic_auth, 38 | address: self.address, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing/random.rs: -------------------------------------------------------------------------------- 1 | use derivative::Derivative; 2 | use std::marker::PhantomData; 3 | 4 | use rand::prelude::*; 5 | use rand::rng; 6 | 7 | use crate::cluster::{ClusterMetadata, ConnectionManager}; 8 | use crate::load_balancing::{LoadBalancingStrategy, QueryPlan, Request}; 9 | use crate::transport::CdrsTransport; 10 | 11 | /// Pure random load balancing. 12 | #[derive(Default, Derivative)] 13 | #[derivative(Debug)] 14 | pub struct RandomLoadBalancingStrategy> { 15 | #[derivative(Debug = "ignore")] 16 | _transport: PhantomData, 17 | #[derivative(Debug = "ignore")] 18 | _connection_manager: PhantomData, 19 | } 20 | 21 | impl> RandomLoadBalancingStrategy { 22 | pub fn new() -> Self { 23 | RandomLoadBalancingStrategy { 24 | _transport: Default::default(), 25 | _connection_manager: Default::default(), 26 | } 27 | } 28 | } 29 | 30 | impl> LoadBalancingStrategy 31 | for RandomLoadBalancingStrategy 32 | { 33 | fn query_plan( 34 | &self, 35 | _request: Option, 36 | cluster: &ClusterMetadata, 37 | ) -> QueryPlan { 38 | let mut result = cluster.unignored_nodes(); 39 | 40 | result.shuffle(&mut rng()); 41 | QueryPlan::new(result) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /documentation/deserialization.md: -------------------------------------------------------------------------------- 1 | ### Mapping results into Rust structures 2 | 3 | In order to query information from Cassandra DB and transform results to Rust types and structures, each row in a query result should be transformed leveraging one of following traits provided by CDRS `cdrs_tokio::types::{AsRustType, AsRust, IntoRustByName, ByName, IntoRustByIndex, ByIndex}`. 4 | 5 | - `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. 6 | 7 | - `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. 8 | 9 | - `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. 10 | 11 | - `ByName` trait is the same as `IntoRustByName` but value should be neither non-set nor null. Otherwise, it panics. 12 | 13 | - `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. 14 | 15 | - `ByIndex` is the same as `IntoRustByIndex` but value can be neither non-set nor null. Otherwise, it panics. 16 | 17 | Relations between Cassandra and Rust types are described in [type-mapping](type-mapping.md). For details see examples. 18 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing.rs: -------------------------------------------------------------------------------- 1 | mod initializing_wrapper; 2 | pub mod node_distance_evaluator; 3 | mod random; 4 | mod request; 5 | mod round_robin; 6 | mod topology_aware; 7 | 8 | pub(crate) use self::initializing_wrapper::InitializingWrapperLoadBalancingStrategy; 9 | pub use self::random::RandomLoadBalancingStrategy; 10 | pub use self::request::Request; 11 | pub use self::round_robin::RoundRobinLoadBalancingStrategy; 12 | pub use self::topology_aware::TopologyAwareLoadBalancingStrategy; 13 | use crate::cluster::topology::Node; 14 | use crate::cluster::{ClusterMetadata, ConnectionManager}; 15 | use crate::transport::CdrsTransport; 16 | use derive_more::Constructor; 17 | use std::sync::Arc; 18 | 19 | #[derive(Debug, Constructor, Default)] 20 | pub struct QueryPlan + 'static> { 21 | pub nodes: Vec>>, 22 | } 23 | 24 | impl> Clone for QueryPlan { 25 | fn clone(&self) -> Self { 26 | QueryPlan::new(self.nodes.clone()) 27 | } 28 | } 29 | 30 | /// Load balancing strategy, usually used for managing target node connections. 31 | pub trait LoadBalancingStrategy> { 32 | /// Returns query plan for given request. If no request is given, return a generic plan for 33 | /// establishing connection(s) to node(s). 34 | fn query_plan( 35 | &self, 36 | request: Option, 37 | cluster: &ClusterMetadata, 38 | ) -> QueryPlan; 39 | } 40 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/keyspace_holder.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use arc_swap::ArcSwapOption; 4 | use tokio::sync::watch::Sender; 5 | 6 | /// Holds currently set global keyspace. 7 | #[derive(Debug)] 8 | pub struct KeyspaceHolder { 9 | current_keyspace: ArcSwapOption, 10 | keyspace_sender: Sender>, 11 | } 12 | 13 | impl KeyspaceHolder { 14 | pub fn new(keyspace_sender: Sender>) -> Self { 15 | KeyspaceHolder { 16 | current_keyspace: Default::default(), 17 | keyspace_sender, 18 | } 19 | } 20 | 21 | #[inline] 22 | pub fn current_keyspace(&self) -> Option> { 23 | self.current_keyspace.load().clone() 24 | } 25 | 26 | #[inline] 27 | pub fn update_current_keyspace(&self, keyspace: String) { 28 | let old_keyspace = self.current_keyspace.swap(Some(Arc::new(keyspace.clone()))); 29 | match &old_keyspace { 30 | None => { 31 | self.send_notification(keyspace); 32 | } 33 | Some(old_keyspace) if **old_keyspace != keyspace => { 34 | self.send_notification(keyspace); 35 | } 36 | _ => {} 37 | } 38 | } 39 | 40 | #[inline] 41 | pub fn update_current_keyspace_without_notification(&self, keyspace: String) { 42 | self.current_keyspace.store(Some(Arc::new(keyspace))); 43 | } 44 | 45 | #[inline] 46 | fn send_notification(&self, keyspace: String) { 47 | let _ = self.keyspace_sender.send(Some(keyspace)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing/initializing_wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::cluster::topology::Node; 4 | use crate::cluster::{ClusterMetadata, ConnectionManager}; 5 | use crate::load_balancing::{LoadBalancingStrategy, QueryPlan, Request}; 6 | use crate::transport::CdrsTransport; 7 | 8 | // Wrapper strategy which returns contact points until cluster metadata gets populated. 9 | pub struct InitializingWrapperLoadBalancingStrategy< 10 | T: CdrsTransport + 'static, 11 | CM: ConnectionManager + 'static, 12 | LB: LoadBalancingStrategy, 13 | > { 14 | inner: LB, 15 | contact_points_query_plan: QueryPlan, 16 | } 17 | 18 | impl, LB: LoadBalancingStrategy> 19 | LoadBalancingStrategy for InitializingWrapperLoadBalancingStrategy 20 | { 21 | fn query_plan( 22 | &self, 23 | request: Option, 24 | cluster: &ClusterMetadata, 25 | ) -> QueryPlan { 26 | if cluster.has_nodes() { 27 | self.inner.query_plan(request, cluster) 28 | } else { 29 | self.contact_points_query_plan.clone() 30 | } 31 | } 32 | } 33 | 34 | impl, LB: LoadBalancingStrategy> 35 | InitializingWrapperLoadBalancingStrategy 36 | { 37 | pub fn new(inner: LB, contact_points: Vec>>) -> Self { 38 | InitializingWrapperLoadBalancingStrategy { 39 | inner, 40 | contact_points_query_plan: QueryPlan::new(contact_points), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/node_address.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use std::net::SocketAddr; 3 | use tokio::net::lookup_host; 4 | 5 | use cassandra_protocol::error::Result; 6 | 7 | /// Representation of a node address. Can be a direct socket address or a hostname. In the latter 8 | /// case, the host can be resolved to multiple addresses, which could result in multiple node 9 | /// configurations. 10 | #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, Debug)] 11 | pub enum NodeAddress { 12 | Direct(SocketAddr), 13 | Hostname(String), 14 | } 15 | 16 | impl From for NodeAddress { 17 | fn from(addr: SocketAddr) -> Self { 18 | NodeAddress::Direct(addr) 19 | } 20 | } 21 | 22 | impl From for NodeAddress { 23 | fn from(value: String) -> Self { 24 | NodeAddress::Hostname(value) 25 | } 26 | } 27 | 28 | impl From<&String> for NodeAddress { 29 | fn from(value: &String) -> Self { 30 | NodeAddress::Hostname(value.clone()) 31 | } 32 | } 33 | 34 | impl From<&str> for NodeAddress { 35 | fn from(value: &str) -> Self { 36 | NodeAddress::Hostname(value.to_string()) 37 | } 38 | } 39 | 40 | impl NodeAddress { 41 | /// Resolves this address to socket addresses. 42 | pub async fn resolve_address(&self) -> Result> { 43 | match self { 44 | NodeAddress::Direct(addr) => Ok(vec![*addr]), 45 | NodeAddress::Hostname(hostname) => lookup_host(hostname) 46 | .await 47 | .map(|addrs| addrs.collect()) 48 | .map_err(Into::into), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_options.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 3 | use std::io::Cursor; 4 | 5 | /// The structure which represents a body of a envelope of type `options`. 6 | #[derive(Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)] 7 | pub struct BodyReqOptions; 8 | 9 | impl Serialize for BodyReqOptions { 10 | #[inline(always)] 11 | fn serialize(&self, _cursor: &mut Cursor<&mut Vec>, _version: Version) {} 12 | } 13 | 14 | impl FromCursor for BodyReqOptions { 15 | #[inline(always)] 16 | fn from_cursor(_cursor: &mut Cursor<&[u8]>, _version: Version) -> error::Result { 17 | Ok(BodyReqOptions) 18 | } 19 | } 20 | 21 | impl Envelope { 22 | /// Creates new envelope of type `options`. 23 | pub fn new_req_options(version: Version) -> Envelope { 24 | let direction = Direction::Request; 25 | let opcode = Opcode::Options; 26 | let body: BodyReqOptions = Default::default(); 27 | 28 | Envelope::new( 29 | version, 30 | direction, 31 | Flags::empty(), 32 | opcode, 33 | 0, 34 | body.serialize_to_vec(version), 35 | None, 36 | vec![], 37 | ) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | 45 | #[test] 46 | fn test_frame_options() { 47 | let frame = Envelope::new_req_options(Version::V4); 48 | assert_eq!(frame.version, Version::V4); 49 | assert_eq!(frame.opcode, Opcode::Options); 50 | assert!(frame.body.is_empty()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing/round_robin.rs: -------------------------------------------------------------------------------- 1 | use derivative::Derivative; 2 | use std::marker::PhantomData; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use crate::cluster::{ClusterMetadata, ConnectionManager}; 6 | use crate::load_balancing::{LoadBalancingStrategy, QueryPlan, Request}; 7 | use crate::transport::CdrsTransport; 8 | 9 | /// Round-robin load balancing. 10 | #[derive(Derivative, Default)] 11 | #[derivative(Debug)] 12 | pub struct RoundRobinLoadBalancingStrategy> { 13 | prev_idx: AtomicUsize, 14 | #[derivative(Debug = "ignore")] 15 | _transport: PhantomData, 16 | #[derivative(Debug = "ignore")] 17 | _connection_manager: PhantomData, 18 | } 19 | 20 | impl> RoundRobinLoadBalancingStrategy { 21 | pub fn new() -> Self { 22 | RoundRobinLoadBalancingStrategy { 23 | prev_idx: AtomicUsize::new(0), 24 | _transport: Default::default(), 25 | _connection_manager: Default::default(), 26 | } 27 | } 28 | } 29 | 30 | impl> LoadBalancingStrategy 31 | for RoundRobinLoadBalancingStrategy 32 | { 33 | fn query_plan( 34 | &self, 35 | _request: Option, 36 | cluster: &ClusterMetadata, 37 | ) -> QueryPlan { 38 | let mut nodes = cluster.unignored_nodes(); 39 | if nodes.is_empty() { 40 | return QueryPlan::new(nodes); 41 | } 42 | 43 | let cur_idx = self.prev_idx.fetch_add(1, Ordering::SeqCst) % nodes.len(); 44 | 45 | nodes.rotate_left(cur_idx); 46 | QueryPlan::new(nodes) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/src/db_mirror.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use proc_macro2::TokenStream; 3 | use quote::*; 4 | use syn::spanned::Spanned; 5 | use syn::{DeriveInput, Error, Result}; 6 | 7 | use crate::common::struct_fields; 8 | 9 | pub fn impl_db_mirror(ast: &DeriveInput) -> Result { 10 | let name = &ast.ident; 11 | let idents: Vec<_> = struct_fields(ast)? 12 | .named 13 | .iter() 14 | .map(|f| { 15 | f.ident 16 | .clone() 17 | .ok_or_else(|| Error::new(f.span(), "Expected a named field!")) 18 | }) 19 | .try_collect()?; 20 | let idents_copy = idents.clone(); 21 | 22 | let fields = idents 23 | .iter() 24 | .map(|i| i.to_string()) 25 | .collect::>(); 26 | let names = fields.join(", "); 27 | let question_marks = fields 28 | .iter() 29 | .map(|_| "?".to_string()) 30 | .collect::>() 31 | .join(", "); 32 | 33 | Ok(quote! { 34 | impl #name { 35 | pub fn insert_query() -> &'static str { 36 | concat!("insert into ", stringify!(#name), "(", 37 | #names, 38 | ") values (", 39 | #question_marks, 40 | ")") 41 | } 42 | 43 | pub fn into_query_values(self) -> cdrs_tokio::query::QueryValues { 44 | use std::collections::HashMap; 45 | let mut values: HashMap = HashMap::new(); 46 | 47 | #( 48 | values.insert(stringify!(#idents).to_string(), self.#idents_copy.into()); 49 | )* 50 | 51 | cdrs_tokio::query::QueryValues::NamedValues(values) 52 | } 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_authenticate.rs: -------------------------------------------------------------------------------- 1 | use super::Serialize; 2 | use crate::error; 3 | use crate::frame::{FromCursor, Version}; 4 | use crate::types::{from_cursor_str, serialize_str}; 5 | use std::io::Cursor; 6 | 7 | /// A server authentication challenge. 8 | #[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone)] 9 | pub struct BodyResAuthenticate { 10 | pub data: String, 11 | } 12 | 13 | impl Serialize for BodyResAuthenticate { 14 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 15 | serialize_str(cursor, &self.data, version); 16 | } 17 | } 18 | 19 | impl FromCursor for BodyResAuthenticate { 20 | fn from_cursor( 21 | cursor: &mut Cursor<&[u8]>, 22 | _version: Version, 23 | ) -> error::Result { 24 | Ok(BodyResAuthenticate { 25 | data: from_cursor_str(cursor)?.to_string(), 26 | }) 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use crate::frame::traits::FromCursor; 34 | use crate::frame::Version; 35 | use std::io::Cursor; 36 | 37 | #[test] 38 | fn body_res_authenticate() { 39 | // string "abcde" 40 | let bytes = [0, 5, 97, 98, 99, 100, 101]; 41 | let expected = BodyResAuthenticate { 42 | data: "abcde".into(), 43 | }; 44 | 45 | { 46 | let mut cursor: Cursor<&[u8]> = Cursor::new(&bytes); 47 | let auth = BodyResAuthenticate::from_cursor(&mut cursor, Version::V4).unwrap(); 48 | assert_eq!(auth, expected); 49 | } 50 | 51 | { 52 | let mut buffer = Vec::new(); 53 | let mut cursor = Cursor::new(&mut buffer); 54 | expected.serialize(&mut cursor, Version::V4); 55 | assert_eq!(buffer, bytes); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/prepared_query.rs: -------------------------------------------------------------------------------- 1 | use arc_swap::ArcSwapOption; 2 | use std::cmp::Ordering; 3 | use std::hash::{Hash, Hasher}; 4 | 5 | use crate::types::CBytesShort; 6 | 7 | #[derive(Debug)] 8 | pub struct PreparedQuery { 9 | pub id: CBytesShort, 10 | pub query: String, 11 | pub keyspace: Option, 12 | pub pk_indexes: Vec, 13 | pub result_metadata_id: ArcSwapOption, 14 | } 15 | 16 | impl Clone for PreparedQuery { 17 | fn clone(&self) -> Self { 18 | Self { 19 | id: self.id.clone(), 20 | query: self.query.clone(), 21 | keyspace: self.keyspace.clone(), 22 | pk_indexes: self.pk_indexes.clone(), 23 | result_metadata_id: ArcSwapOption::new(self.result_metadata_id.load().clone()), 24 | } 25 | } 26 | } 27 | 28 | impl PartialEq for PreparedQuery { 29 | #[inline] 30 | fn eq(&self, other: &Self) -> bool { 31 | self.id == other.id && *self.result_metadata_id.load() == *other.result_metadata_id.load() 32 | } 33 | } 34 | 35 | impl Eq for PreparedQuery {} 36 | 37 | impl PartialOrd for PreparedQuery { 38 | #[inline] 39 | fn partial_cmp(&self, other: &Self) -> Option { 40 | Some(self.cmp(other)) 41 | } 42 | } 43 | 44 | impl Ord for PreparedQuery { 45 | #[inline] 46 | fn cmp(&self, other: &Self) -> Ordering { 47 | match self.id.cmp(&other.id) { 48 | Ordering::Equal => self 49 | .result_metadata_id 50 | .load() 51 | .cmp(&other.result_metadata_id.load()), 52 | result => result, 53 | } 54 | } 55 | } 56 | 57 | impl Hash for PreparedQuery { 58 | #[inline] 59 | fn hash(&self, state: &mut H) { 60 | self.id.hash(state); 61 | self.result_metadata_id.load().hash(state); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/list.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | use itertools::Itertools; 3 | use num_bigint::BigInt; 4 | use std::net::IpAddr; 5 | use uuid::Uuid; 6 | 7 | use crate::error::{Error, Result}; 8 | use crate::frame::message_result::{ColType, ColTypeOption, ColTypeOptionValue}; 9 | use crate::frame::Version; 10 | use crate::types::blob::Blob; 11 | use crate::types::data_serialization_types::*; 12 | use crate::types::decimal::Decimal; 13 | use crate::types::map::Map; 14 | use crate::types::tuple::Tuple; 15 | use crate::types::udt::Udt; 16 | use crate::types::{AsRust, AsRustType, CBytes}; 17 | 18 | // TODO: consider using pointers to ColTypeOption and Vec instead of owning them. 19 | #[derive(Debug, Constructor)] 20 | pub struct List { 21 | /// column spec of the list, i.e. id should be List as it's a list and value should contain 22 | /// a type of list items. 23 | metadata: ColTypeOption, 24 | data: Vec, 25 | protocol_version: Version, 26 | } 27 | 28 | impl List { 29 | fn map(&self, f: F) -> Vec 30 | where 31 | F: FnMut(&CBytes) -> T, 32 | { 33 | self.data.iter().map(f).collect() 34 | } 35 | 36 | fn try_map(&self, f: F) -> Result> 37 | where 38 | F: FnMut(&CBytes) -> Result, 39 | { 40 | self.data.iter().map(f).try_collect() 41 | } 42 | } 43 | 44 | impl AsRust for List {} 45 | 46 | list_as_rust!(Blob); 47 | list_as_rust!(String); 48 | list_as_rust!(bool); 49 | list_as_rust!(i64); 50 | list_as_rust!(i32); 51 | list_as_rust!(i16); 52 | list_as_rust!(i8); 53 | list_as_rust!(f64); 54 | list_as_rust!(f32); 55 | list_as_rust!(IpAddr); 56 | list_as_rust!(Uuid); 57 | list_as_rust!(List); 58 | list_as_rust!(Map); 59 | list_as_rust!(Udt); 60 | list_as_rust!(Tuple); 61 | list_as_rust!(Decimal); 62 | list_as_rust!(BigInt); 63 | 64 | list_as_cassandra_type!(); 65 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_auth_challenge.rs: -------------------------------------------------------------------------------- 1 | use super::Serialize; 2 | use crate::error; 3 | use crate::frame::{FromCursor, Version}; 4 | use crate::types::CBytes; 5 | use std::io::Cursor; 6 | 7 | /// Server authentication challenge. 8 | #[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone)] 9 | pub struct BodyResAuthChallenge { 10 | pub data: CBytes, 11 | } 12 | 13 | impl Serialize for BodyResAuthChallenge { 14 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 15 | self.data.serialize(cursor, version); 16 | } 17 | } 18 | 19 | impl FromCursor for BodyResAuthChallenge { 20 | fn from_cursor( 21 | cursor: &mut Cursor<&[u8]>, 22 | version: Version, 23 | ) -> error::Result { 24 | CBytes::from_cursor(cursor, version).map(|data| BodyResAuthChallenge { data }) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use crate::frame::traits::FromCursor; 32 | use std::io::Cursor; 33 | 34 | #[test] 35 | fn body_res_auth_challenge_from_cursor() { 36 | let bytes = &[0, 0, 0, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 37 | let expected = BodyResAuthChallenge { 38 | data: CBytes::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), 39 | }; 40 | 41 | { 42 | let mut cursor: Cursor<&[u8]> = Cursor::new(bytes); 43 | let body = BodyResAuthChallenge::from_cursor(&mut cursor, Version::V4).unwrap(); 44 | assert_eq!( 45 | body.data.into_bytes().unwrap(), 46 | vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 47 | ); 48 | } 49 | 50 | { 51 | let mut buffer = Vec::new(); 52 | let mut cursor = Cursor::new(&mut buffer); 53 | expected.serialize(&mut cursor, Version::V4); 54 | assert_eq!(buffer, bytes); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_auth_success.rs: -------------------------------------------------------------------------------- 1 | use super::Serialize; 2 | use crate::error; 3 | use crate::frame::{FromCursor, Version}; 4 | use crate::types::CBytes; 5 | use std::io::Cursor; 6 | 7 | /// `BodyReqAuthSuccess` is a envelope that represents a successful authentication response. 8 | #[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone)] 9 | pub struct BodyReqAuthSuccess { 10 | pub data: CBytes, 11 | } 12 | 13 | impl Serialize for BodyReqAuthSuccess { 14 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 15 | self.data.serialize(cursor, version); 16 | } 17 | } 18 | 19 | impl FromCursor for BodyReqAuthSuccess { 20 | fn from_cursor( 21 | cursor: &mut Cursor<&[u8]>, 22 | version: Version, 23 | ) -> error::Result { 24 | CBytes::from_cursor(cursor, version).map(|data| BodyReqAuthSuccess { data }) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use crate::frame::traits::FromCursor; 32 | use std::io::Cursor; 33 | 34 | #[test] 35 | fn body_req_auth_success() { 36 | let bytes = &[0, 0, 0, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 37 | let expected = BodyReqAuthSuccess { 38 | data: CBytes::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), 39 | }; 40 | 41 | { 42 | let mut cursor: Cursor<&[u8]> = Cursor::new(bytes); 43 | let body = BodyReqAuthSuccess::from_cursor(&mut cursor, Version::V4).unwrap(); 44 | assert_eq!( 45 | body.data.into_bytes().unwrap(), 46 | vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 47 | ); 48 | } 49 | 50 | { 51 | let mut buffer = Vec::new(); 52 | let mut cursor = Cursor::new(&mut buffer); 53 | expected.serialize(&mut cursor, Version::V4); 54 | assert_eq!(buffer, bytes); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /documentation/cdrs-session.md: -------------------------------------------------------------------------------- 1 | # CDRS Session 2 | 3 | `Session` is a structure that holds as set pools of connections authorised by a Cluster. As well, it provides data decompressing and load balancing mechanisms used for Cassandra frame exchange, or querying in other words. 4 | 5 | In order to create new session a [cluster config](./cluster-configuration.md) and a load balancing strategy must be provided. Load balancing strategy is used when some query should be performed by driver. At that moment load balancer returns a connection for a node that was picked up in accordance to a strategy. 6 | Such logic guarantees that nodes' loads are balanced and there is no need to establish new connection if there is a one that is released after previous query. 7 | 8 | ## Load balancing 9 | 10 | Any structure that implements `LoadBalancingStrategy` trait can be used in `Session` as a load balancer. 11 | 12 | CDRS provides few strategies out of the box so no additional development may not be needed: 13 | 14 | - `RandomLoadBalancingStrategy` randomly picks up a node from a cluster. 15 | 16 | - `RoundRobinLoadBalancingStrategy` thread safe round-robin balancing strategy. 17 | 18 | - `TopologyAwareLoadBalancingStrategy` policy taking dynamic cluster topology into account. 19 | 20 | Along with that any custom load balancing strategy may be implemented and used with CDRS. The only requirement is the structure must implement `LoadBalancingStrategy` trait. 21 | 22 | ## Data compression 23 | 24 | CQL binary protocol allows using LZ4 and Snappy (for protocol version < 5) data compression in order to reduce traffic between Node and Client. 25 | 26 | CDRS provides methods for creating `Session` with different compression contexts: LZ4 and Snappy. 27 | 28 | ### Reference 29 | 30 | 1. LZ4 compression algorithm https://en.wikipedia.org/wiki/LZ4_(compression_algorithm). 31 | 32 | 2. Snappy compression algorithm https://en.wikipedia.org/wiki/Snappy_(compression). 33 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This trait provides functionality for derivation `IntoCDRSBytes` trait implementation 2 | //! for underlying 3 | use proc_macro::TokenStream; 4 | use syn::{parse_macro_input, DeriveInput, Error}; 5 | 6 | mod common; 7 | mod db_mirror; 8 | mod into_cdrs_value; 9 | mod try_from_row; 10 | mod try_from_udt; 11 | 12 | use crate::db_mirror::impl_db_mirror; 13 | use crate::into_cdrs_value::impl_into_cdrs_value; 14 | use crate::try_from_row::impl_try_from_row; 15 | use crate::try_from_udt::impl_try_from_udt; 16 | 17 | #[proc_macro_derive(DbMirror)] 18 | pub fn db_mirror(input: TokenStream) -> TokenStream { 19 | // Parse the string representation 20 | let ast = parse_macro_input!(input as DeriveInput); 21 | 22 | // Build the impl 23 | impl_db_mirror(&ast) 24 | .unwrap_or_else(Error::into_compile_error) 25 | .into() 26 | } 27 | 28 | #[proc_macro_derive(IntoCdrsValue)] 29 | pub fn into_cdrs_value(input: TokenStream) -> TokenStream { 30 | // Parse the string representation 31 | let ast = parse_macro_input!(input as DeriveInput); 32 | 33 | // Build the impl 34 | impl_into_cdrs_value(&ast) 35 | .unwrap_or_else(Error::into_compile_error) 36 | .into() 37 | } 38 | 39 | #[proc_macro_derive(TryFromRow)] 40 | pub fn try_from_row(input: TokenStream) -> TokenStream { 41 | // Parse the string representation 42 | let ast = parse_macro_input!(input as DeriveInput); 43 | 44 | // Build the impl 45 | impl_try_from_row(&ast) 46 | .unwrap_or_else(Error::into_compile_error) 47 | .into() 48 | } 49 | 50 | #[proc_macro_derive(TryFromUdt)] 51 | pub fn try_from_udt(input: TokenStream) -> TokenStream { 52 | // Parse the string representation 53 | let ast = parse_macro_input!(input as DeriveInput); 54 | 55 | // Build the impl 56 | impl_try_from_udt(&ast) 57 | .unwrap_or_else(Error::into_compile_error) 58 | .into() 59 | } 60 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/topology/node_state.rs: -------------------------------------------------------------------------------- 1 | use bytemuck::NoUninit; 2 | use derive_more::Display; 3 | 4 | /// The state of a node, as viewed from the driver. 5 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, NoUninit)] 6 | #[repr(u8)] 7 | pub enum NodeState { 8 | /// The driver has never tried to connect to the node, nor received any topology events about it. 9 | /// 10 | /// This happens when nodes are first added to the cluster, and will persist if your 11 | /// [`LoadBalancingStrategy`](crate::load_balancing::LoadBalancingStrategy) decides to ignore 12 | /// them. Since the driver does not connect to them, the only way it can assess their states is 13 | /// from topology events. 14 | Unknown, 15 | /// A node is considered up in either of the following situations: 1) the driver has at least 16 | /// one active connection to the node, or 2) the driver is not actively trying to connect to the 17 | /// node (because it's ignored by the 18 | /// [`LoadBalancingStrategy`](crate::load_balancing::LoadBalancingStrategy)), but it has 19 | /// received a topology event indicating that the node is up. 20 | Up, 21 | /// A node is considered down in either of the following situations: 1) the driver has lost all 22 | /// connections to the node (and is currently trying to reconnect), or 2) the driver is not 23 | /// actively trying to connect to the node (because it's ignored by the 24 | /// [`LoadBalancingStrategy`](crate::load_balancing::LoadBalancingStrategy), but it has received 25 | /// a topology event indicating that the node is down. 26 | Down, 27 | /// The node was forced down externally, the driver will never try to reconnect to it. It can 28 | /// happen when an unrecoverable error happened when connecting to the node (e.g. invalid 29 | /// protocol version) or when something decides the node should not be contacted (e.g. 30 | /// [`LoadBalancingStrategy`](crate::load_balancing::LoadBalancingStrategy)). 31 | ForcedDown, 32 | } 33 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_event.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::events::ServerEvent; 3 | use crate::frame::Serialize; 4 | use crate::frame::{FromCursor, Version}; 5 | use std::io::Cursor; 6 | 7 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 8 | pub struct BodyResEvent { 9 | pub event: ServerEvent, 10 | } 11 | 12 | impl Serialize for BodyResEvent { 13 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 14 | self.event.serialize(cursor, version); 15 | } 16 | } 17 | 18 | impl FromCursor for BodyResEvent { 19 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 20 | let event = ServerEvent::from_cursor(cursor, version)?; 21 | Ok(BodyResEvent { event }) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | use crate::frame::events::*; 29 | use crate::frame::traits::FromCursor; 30 | use std::io::Cursor; 31 | 32 | #[test] 33 | fn body_res_event() { 34 | let bytes = &[ 35 | // TOPOLOGY_CHANGE 36 | 0, 15, 84, 79, 80, 79, 76, 79, 71, 89, 95, 67, 72, 65, 78, 71, 69, // NEW_NODE 37 | 0, 8, 78, 69, 87, 95, 78, 79, 68, 69, // 38 | 4, 127, 0, 0, 1, 0, 0, 0, 1, // inet - 127.0.0.1:1 39 | ]; 40 | let expected = ServerEvent::TopologyChange(TopologyChange { 41 | change_type: TopologyChangeType::NewNode, 42 | addr: "127.0.0.1:1".parse().unwrap(), 43 | }); 44 | 45 | { 46 | let mut cursor: Cursor<&[u8]> = Cursor::new(bytes); 47 | let event = BodyResEvent::from_cursor(&mut cursor, Version::V4) 48 | .unwrap() 49 | .event; 50 | assert_eq!(event, expected); 51 | } 52 | 53 | { 54 | let mut buffer = Vec::new(); 55 | let mut cursor = Cursor::new(&mut buffer); 56 | expected.serialize(&mut cursor, Version::V4); 57 | assert_eq!(buffer, bytes); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cdrs-tokio/src/statement/statement_params.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::query::QueryParams; 2 | use cassandra_protocol::types::value::Value; 3 | use derivative::Derivative; 4 | use std::sync::Arc; 5 | 6 | use crate::cluster::Murmur3Token; 7 | use crate::retry::RetryPolicy; 8 | use crate::speculative_execution::SpeculativeExecutionPolicy; 9 | 10 | /// Parameters of Query for query operation. 11 | #[derive(Default, Clone, Derivative)] 12 | #[derivative(Debug)] 13 | pub struct StatementParams { 14 | /// Protocol-level parameters. 15 | pub query_params: QueryParams, 16 | /// Is the query idempotent. 17 | pub is_idempotent: bool, 18 | /// Query keyspace. If not using a global one, setting it explicitly might help the load 19 | /// balancer use more appropriate nodes. Note: prepared statements with keyspace information 20 | /// take precedence over this field. 21 | pub keyspace: Option, 22 | /// The token to use for token-aware routing. A load balancer may use this information to 23 | /// determine which nodes to contact. Takes precedence over `routing_key`. 24 | pub token: Option, 25 | /// The partition key to use for token-aware routing. A load balancer may use this information 26 | /// to determine which nodes to contact. Alternative to `token`. Note: prepared statements 27 | /// with bound primary key values take precedence over this field. 28 | pub routing_key: Option>, 29 | /// Should tracing be enabled. 30 | pub tracing: bool, 31 | /// Should warnings be enabled. 32 | pub warnings: bool, 33 | /// Custom statement speculative execution policy. 34 | #[derivative(Debug = "ignore")] 35 | pub speculative_execution_policy: Option>, 36 | /// Custom statement retry policy. 37 | #[derivative(Debug = "ignore")] 38 | pub retry_policy: Option>, 39 | /// Enable beta protocol features. Server will respond with ERROR if protocol version is marked 40 | /// as beta on server and client does not provide this flag. 41 | pub beta_protocol: bool, 42 | } 43 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/send_envelope.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::error; 2 | use cassandra_protocol::frame::Envelope; 3 | use std::sync::Arc; 4 | 5 | use crate::cluster::topology::Node; 6 | use crate::cluster::ConnectionManager; 7 | use crate::retry::{QueryInfo, RetryDecision, RetrySession}; 8 | use crate::transport::CdrsTransport; 9 | 10 | /// Mid-level interface for sending envelopes to the cluster. Uses a query plan to route the envelope 11 | /// to the appropriate node, and retry policy for error handling. Returns `None` if no nodes were 12 | /// present in the query plan. 13 | pub async fn send_envelope + 'static>( 14 | query_plan: impl Iterator>>, 15 | envelope: &Envelope, 16 | is_idempotent: bool, 17 | mut retry_session: Box, 18 | ) -> Option> { 19 | let mut result = None; 20 | 21 | 'next_node: for node in query_plan { 22 | loop { 23 | let transport = node.persistent_connection().await; 24 | match transport { 25 | Ok(transport) => match transport.write_envelope(envelope, false).await { 26 | Ok(envelope) => return Some(Ok(envelope)), 27 | Err(error) => { 28 | let query_info = QueryInfo { 29 | error: &error, 30 | is_idempotent, 31 | }; 32 | 33 | match retry_session.decide(query_info) { 34 | RetryDecision::RetrySameNode => continue, 35 | RetryDecision::RetryNextNode => continue 'next_node, 36 | RetryDecision::DontRetry => return Some(Err(error)), 37 | } 38 | } 39 | }, 40 | // save the error, but keep trying, since another node might be up 41 | Err(error) => { 42 | result = Some(Err(error)); 43 | continue 'next_node; 44 | } 45 | } 46 | } 47 | } 48 | 49 | result 50 | } 51 | -------------------------------------------------------------------------------- /cdrs-tokio/src/speculative_execution.rs: -------------------------------------------------------------------------------- 1 | //! Pre-emptively query another node if the current one takes too long to respond. 2 | //! 3 | //! Sometimes a Cassandra node might be experiencing difficulties (ex: long GC pause) and take 4 | //! longer than usual to reply. Queries sent to that node will experience bad latency. 5 | //! 6 | //! One thing we can do to improve that is pre-emptively start a second execution of the query 7 | //! against another node, before the first node has replied or errored out. If that second node 8 | //! replies faster, we can send the response back to the client. We also cancel the first execution. 9 | //! 10 | //! Turning on speculative executions doesn't change the driver's retry behavior. Each parallel 11 | //! execution will trigger retries independently. 12 | 13 | use derive_more::Constructor; 14 | use std::time::Duration; 15 | 16 | /// Current speculative execution context. 17 | #[derive(Constructor, Debug)] 18 | pub struct Context { 19 | pub running_executions: usize, 20 | } 21 | 22 | /// The policy that decides if the driver will send speculative queries to the next nodes when the 23 | /// current node takes too long to respond. If a query is not idempotent, the driver will never 24 | /// schedule speculative executions for it, because there is no way to guarantee that only one node 25 | /// will apply the mutation. 26 | pub trait SpeculativeExecutionPolicy { 27 | /// Returns the time until a speculative request is sent to the next node. `None` means there 28 | /// should not be another execution. 29 | fn execution_interval(&self, context: &Context) -> Option; 30 | } 31 | 32 | /// A policy that schedules a configurable number of speculative executions, separated by a fixed 33 | /// delay. 34 | #[derive(Debug, Clone, Copy, Constructor)] 35 | pub struct ConstantSpeculativeExecutionPolicy { 36 | max_executions: usize, 37 | delay: Duration, 38 | } 39 | 40 | impl SpeculativeExecutionPolicy for ConstantSpeculativeExecutionPolicy { 41 | fn execution_interval(&self, context: &Context) -> Option { 42 | if context.running_executions < self.max_executions { 43 | Some(self.delay) 44 | } else { 45 | None 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /documentation/cluster-configuration.md: -------------------------------------------------------------------------------- 1 | ### Cluster configuration 2 | 3 | Apache Cassandra is designed to be a scalable and higly available database. So most often developers work with multi node Cassandra clusters. For instance Apple's setup includes 75k nodes, Netflix 2.5k nodes, Ebay >100 nodes. 4 | 5 | That's why CDRS driver was designed with multi-node support in mind. In order to connect to Cassandra cluster via CDRS connection configuration should be provided: 6 | 7 | ```rust 8 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 9 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 10 | 11 | fn main() { 12 | let cluster_config = NodeTcpConfigBuilder::new("127.0.0.1:9042".parse().unwrap(), Arc::new(NoneAuthenticatorProvider)).build(); 13 | // ... 14 | } 15 | ``` 16 | 17 | For each node configuration, `SaslAuthenticatorProvider` should be provided. `SaslAuthenticatorProvider` is a trait that the structure should implement so it can be used by CDRS session for authentication. Out of the box CDRS provides two types of authenticators: 18 | 19 | - `cdrs_tokio::authenticators::NoneAuthenticatorProvider` that should be used if authentication is disabled by a node ([Cassandra authenticator](http://cassandra.apache.org/doc/latest/configuration/cassandra_config_file.html#authenticator) is set to `AllowAllAuthenticator`) on server. 20 | 21 | - `cdrs_tokio::authenticators::StaticPasswordAuthenticatorProvider` that should be used if authentication is enabled on the server and [authenticator](http://cassandra.apache.org/doc/latest/configuration/cassandra_config_file.html#authenticator) is `PasswordAuthenticator`. 22 | 23 | ```rust 24 | use cdrs_tokio::authenticators::StaticPasswordAuthenticatorProvider; 25 | let authenticator = StaticPasswordAuthenticatorProvider::new("user", "pass"); 26 | ``` 27 | 28 | If a node has a custom authentication strategy, corresponded `SaslAuthenticatorProvider` should be implemented by a developer and further used in `NodeTcpConfigBuilder`. 29 | 30 | ### Reference 31 | 32 | 1. Cassandra cluster configuration https://docs.datastax.com/en/cassandra/3.0/cassandra/initialize/initTOC.html. 33 | 34 | 2. ScyllaDB cluster configuration https://docs.scylladb.com/operating-scylla/ (see Cluster Management section). 35 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/multithread.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "e2e-tests")] 2 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 9 | #[cfg(feature = "e2e-tests")] 10 | use cdrs_tokio::retry::NeverReconnectionPolicy; 11 | #[cfg(feature = "e2e-tests")] 12 | use std::sync::Arc; 13 | 14 | #[tokio::test] 15 | #[cfg(feature = "e2e-tests")] 16 | async fn multithread() { 17 | let cluster_config = NodeTcpConfigBuilder::new() 18 | .with_contact_point("127.0.0.1:9042".into()) 19 | .with_authenticator_provider(Arc::new(NoneAuthenticatorProvider)) 20 | .build() 21 | .await 22 | .unwrap(); 23 | let no_compression = 24 | TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 25 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 26 | .build() 27 | .await 28 | .unwrap(); 29 | 30 | no_compression.query("CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };").await.expect("Could not create ks"); 31 | no_compression 32 | .query("use test_ks;") 33 | .await 34 | .expect("Keyspace create error"); 35 | no_compression.query("create table if not exists user (user_id int primary key) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };").await.expect("Could not create table"); 36 | 37 | let arc = Arc::new(no_compression); 38 | let mut handles = vec![]; 39 | 40 | for _ in 0..100 { 41 | let c = Arc::clone(&arc); 42 | 43 | handles.push(tokio::spawn( 44 | async move { c.query("select * from user").await }, 45 | )); 46 | } 47 | 48 | for task in handles { 49 | let result = task.await.unwrap(); 50 | 51 | match result { 52 | Ok(_) => { 53 | println!("Query went OK"); 54 | } 55 | Err(e) => { 56 | panic!("Query error: {:#?}", e); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/vector.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::frame::message_result::{ColType, ColTypeOption, ColTypeOptionValue}; 3 | use crate::frame::Version; 4 | use crate::types::data_serialization_types::*; 5 | use crate::types::{AsRust, AsRustType, CBytes}; 6 | use derive_more::Constructor; 7 | use itertools::Itertools; 8 | 9 | // TODO: consider using pointers to ColTypeOption and Vec instead of owning them. 10 | #[derive(Debug, Constructor)] 11 | pub struct Vector { 12 | /// column spec of the list, i.e. id should be List as it's a list and value should contain 13 | /// a type of list items. 14 | metadata: ColTypeOption, 15 | data: Vec, 16 | protocol_version: Version, 17 | } 18 | 19 | impl Vector { 20 | fn try_map(&self, f: F) -> Result> 21 | where 22 | F: FnMut(&CBytes) -> Result, 23 | { 24 | self.data.iter().map(f).try_collect() 25 | } 26 | } 27 | 28 | pub struct VectorInfo { 29 | pub internal_type: String, 30 | pub count: usize, 31 | } 32 | 33 | pub fn get_vector_type_info(option_value: &ColTypeOptionValue) -> Result { 34 | let input = match option_value { 35 | ColTypeOptionValue::CString(ref s) => s, 36 | _ => return Err(Error::General("Option value must be a string!".into())), 37 | }; 38 | 39 | let _custom_type = input.split('(').next().unwrap().rsplit('.').next().unwrap(); 40 | 41 | let vector_type = input 42 | .split('(') 43 | .nth(1) 44 | .and_then(|s| s.split(',').next()) 45 | .and_then(|s| s.rsplit('.').next()) 46 | .map(|s| s.trim()) 47 | .ok_or_else(|| Error::General("Cannot parse vector type!".into()))?; 48 | 49 | let count: usize = input 50 | .split('(') 51 | .nth(1) 52 | .and_then(|s| s.rsplit(',').next()) 53 | .and_then(|s| s.split(')').next()) 54 | .map(|s| s.trim().parse()) 55 | .transpose() 56 | .map_err(|_| Error::General("Cannot parse vector count!".to_string()))? 57 | .ok_or_else(|| Error::General("Cannot parse vector count!".into()))?; 58 | 59 | Ok(VectorInfo { 60 | internal_type: vector_type.to_string(), 61 | count, 62 | }) 63 | } 64 | 65 | impl AsRust for Vector {} 66 | 67 | vector_as_rust!(f32); 68 | 69 | vector_as_cassandra_type!(); 70 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_auth_response.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 3 | use crate::types::CBytes; 4 | use derive_more::Constructor; 5 | use std::io::Cursor; 6 | 7 | #[derive(Debug, Constructor, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 8 | pub struct BodyReqAuthResponse { 9 | pub data: CBytes, 10 | } 11 | 12 | impl Serialize for BodyReqAuthResponse { 13 | #[inline] 14 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 15 | self.data.serialize(cursor, version); 16 | } 17 | } 18 | 19 | impl FromCursor for BodyReqAuthResponse { 20 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 21 | CBytes::from_cursor(cursor, version).map(BodyReqAuthResponse::new) 22 | } 23 | } 24 | 25 | impl Envelope { 26 | /// Creates new envelope of type `AuthResponse`. 27 | pub fn new_req_auth_response(token_bytes: CBytes, version: Version) -> Envelope { 28 | let direction = Direction::Request; 29 | let opcode = Opcode::AuthResponse; 30 | let body = BodyReqAuthResponse::new(token_bytes); 31 | 32 | Envelope::new( 33 | version, 34 | direction, 35 | Flags::empty(), 36 | opcode, 37 | 0, 38 | body.serialize_to_vec(version), 39 | None, 40 | vec![], 41 | ) 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use crate::types::CBytes; 49 | 50 | #[test] 51 | fn body_req_auth_response() { 52 | let bytes = CBytes::new(vec![1, 2, 3]); 53 | let body = BodyReqAuthResponse::new(bytes); 54 | assert_eq!( 55 | body.serialize_to_vec(Version::V4), 56 | vec![0, 0, 0, 3, 1, 2, 3] 57 | ); 58 | } 59 | 60 | #[test] 61 | fn frame_body_req_auth_response() { 62 | let bytes = vec![1, 2, 3]; 63 | let frame = Envelope::new_req_auth_response(CBytes::new(bytes), Version::V4); 64 | 65 | assert_eq!(frame.version, Version::V4); 66 | assert_eq!(frame.opcode, Opcode::AuthResponse); 67 | assert_eq!(frame.body, &[0, 0, 0, 3, 1, 2, 3]); 68 | assert_eq!(frame.tracing_id, None); 69 | assert!(frame.warnings.is_empty()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/udt.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::IpAddr; 3 | use std::num::{NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8}; 4 | 5 | use chrono::prelude::*; 6 | use time::PrimitiveDateTime; 7 | use uuid::Uuid; 8 | 9 | use crate::error::{column_is_empty_err, Error, Result}; 10 | use crate::frame::message_result::{CUdt, ColType, ColTypeOption, ColTypeOptionValue}; 11 | use crate::frame::Version; 12 | use crate::types::blob::Blob; 13 | use crate::types::data_serialization_types::*; 14 | use crate::types::decimal::Decimal; 15 | use crate::types::list::List; 16 | use crate::types::map::Map; 17 | use crate::types::tuple::Tuple; 18 | use crate::types::{ByName, CBytes, IntoRustByName}; 19 | use num_bigint::BigInt; 20 | 21 | #[derive(Clone, Debug)] 22 | pub struct Udt { 23 | data: HashMap, 24 | protocol_version: Version, 25 | } 26 | 27 | impl Udt { 28 | pub fn new(fields: Vec, metadata: &CUdt, protocol_version: Version) -> Udt { 29 | let mut data: HashMap = 30 | HashMap::with_capacity(metadata.descriptions.len()); 31 | 32 | for ((name, val_type), val_b) in metadata.descriptions.iter().zip(fields.into_iter()) { 33 | data.insert(name.clone(), (val_type.clone(), val_b)); 34 | } 35 | Udt { 36 | data, 37 | protocol_version, 38 | } 39 | } 40 | } 41 | 42 | impl ByName for Udt {} 43 | 44 | into_rust_by_name!(Udt, Blob); 45 | into_rust_by_name!(Udt, String); 46 | into_rust_by_name!(Udt, bool); 47 | into_rust_by_name!(Udt, i64); 48 | into_rust_by_name!(Udt, i32); 49 | into_rust_by_name!(Udt, i16); 50 | into_rust_by_name!(Udt, i8); 51 | into_rust_by_name!(Udt, f64); 52 | into_rust_by_name!(Udt, f32); 53 | into_rust_by_name!(Udt, IpAddr); 54 | into_rust_by_name!(Udt, Uuid); 55 | into_rust_by_name!(Udt, List); 56 | into_rust_by_name!(Udt, Map); 57 | into_rust_by_name!(Udt, Udt); 58 | into_rust_by_name!(Udt, Tuple); 59 | into_rust_by_name!(Udt, PrimitiveDateTime); 60 | into_rust_by_name!(Udt, Decimal); 61 | into_rust_by_name!(Udt, NonZeroI8); 62 | into_rust_by_name!(Udt, NonZeroI16); 63 | into_rust_by_name!(Udt, NonZeroI32); 64 | into_rust_by_name!(Udt, NonZeroI64); 65 | into_rust_by_name!(Udt, NaiveDateTime); 66 | into_rust_by_name!(Udt, DateTime); 67 | into_rust_by_name!(Udt, BigInt); 68 | 69 | udt_as_cassandra_type!(); 70 | -------------------------------------------------------------------------------- /cdrs-tokio/src/frame_encoding.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::compression::Compression; 2 | use cassandra_protocol::frame::frame_decoder::{ 3 | FrameDecoder, LegacyFrameDecoder, Lz4FrameDecoder, UncompressedFrameDecoder, 4 | }; 5 | use cassandra_protocol::frame::frame_encoder::{ 6 | FrameEncoder, LegacyFrameEncoder, Lz4FrameEncoder, UncompressedFrameEncoder, 7 | }; 8 | use cassandra_protocol::frame::Version; 9 | 10 | /// A factory for frame encoder/decoder. 11 | pub trait FrameEncodingFactory { 12 | /// Creates a new frame encoder based on given protocol settings. 13 | fn create_encoder( 14 | &self, 15 | version: Version, 16 | compression: Compression, 17 | ) -> Box; 18 | 19 | /// Creates a new frame decoder based on given protocol settings. 20 | fn create_decoder( 21 | &self, 22 | version: Version, 23 | compression: Compression, 24 | ) -> Box; 25 | } 26 | 27 | /// Frame encoding factor based on protocol settings. 28 | #[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)] 29 | pub struct ProtocolFrameEncodingFactory; 30 | 31 | impl FrameEncodingFactory for ProtocolFrameEncodingFactory { 32 | fn create_encoder( 33 | &self, 34 | version: Version, 35 | compression: Compression, 36 | ) -> Box { 37 | if version >= Version::V5 { 38 | match compression { 39 | Compression::Lz4 => Box::::default(), 40 | // >= v5 supports only lz4 => fall back to uncompressed 41 | _ => Box::::default(), 42 | } 43 | } else { 44 | Box::::default() 45 | } 46 | } 47 | 48 | fn create_decoder( 49 | &self, 50 | version: Version, 51 | compression: Compression, 52 | ) -> Box { 53 | if version >= Version::V5 { 54 | match compression { 55 | Compression::Lz4 => Box::::default(), 56 | // >= v5 supports only lz4 => fall back to uncompressed 57 | _ => Box::::default(), 58 | } 59 | } else { 60 | Box::::default() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/query_flags.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use std::io::{Cursor, Read}; 3 | 4 | use crate::error; 5 | use crate::frame::{FromCursor, Serialize, Version}; 6 | use crate::types::INT_LEN; 7 | 8 | bitflags! { 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 10 | pub struct QueryFlags: u32 { 11 | /// Indicates that Query Params contain value. 12 | const VALUE = 0x001; 13 | /// Indicates that Query Params does not contain metadata. 14 | const SKIP_METADATA = 0x002; 15 | /// Indicates that Query Params contain page size. 16 | const PAGE_SIZE = 0x004; 17 | /// Indicates that Query Params contain paging state. 18 | const WITH_PAGING_STATE = 0x008; 19 | /// Indicates that Query Params contain serial consistency. 20 | const WITH_SERIAL_CONSISTENCY = 0x010; 21 | /// Indicates that Query Params contain default timestamp. 22 | const WITH_DEFAULT_TIMESTAMP = 0x020; 23 | /// Indicates that Query Params values are named ones. 24 | const WITH_NAMES_FOR_VALUES = 0x040; 25 | /// Indicates that Query Params contain keyspace name. 26 | const WITH_KEYSPACE = 0x080; 27 | /// Indicates that Query Params contain "now" in seconds. 28 | const WITH_NOW_IN_SECONDS = 0x100; 29 | } 30 | } 31 | 32 | impl Default for QueryFlags { 33 | #[inline] 34 | fn default() -> Self { 35 | QueryFlags::empty() 36 | } 37 | } 38 | 39 | impl Serialize for QueryFlags { 40 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 41 | if version >= Version::V5 { 42 | self.bits().serialize(cursor, version); 43 | } else { 44 | (self.bits() as u8).serialize(cursor, version); 45 | } 46 | } 47 | } 48 | 49 | impl FromCursor for QueryFlags { 50 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 51 | if version >= Version::V5 { 52 | let mut buff = [0; INT_LEN]; 53 | cursor 54 | .read_exact(&mut buff) 55 | .map(|()| QueryFlags::from_bits_truncate(u32::from_be_bytes(buff))) 56 | .map_err(|error| error.into()) 57 | } else { 58 | let mut buff = [0]; 59 | cursor.read_exact(&mut buff)?; 60 | Ok(QueryFlags::from_bits_truncate(buff[0] as u32)) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/query_values.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "e2e-tests")] 4 | use cassandra_protocol::frame::Version; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::query_values; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::types::IntoRustByName; 9 | #[cfg(feature = "e2e-tests")] 10 | use common::*; 11 | 12 | #[tokio::test] 13 | #[cfg(feature = "e2e-tests")] 14 | async fn query_values_in_v4() { 15 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.test_query_values_in \ 16 | (id text PRIMARY KEY)"; 17 | let session = setup(cql, Version::V4).await.expect("setup"); 18 | 19 | query_values_in_test(cql, session).await; 20 | } 21 | 22 | #[tokio::test] 23 | #[cfg(feature = "e2e-tests")] 24 | async fn query_values_in_v5() { 25 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.test_query_values_in \ 26 | (id text PRIMARY KEY)"; 27 | let session = setup(cql, Version::V5).await.expect("setup"); 28 | 29 | query_values_in_test(cql, session).await; 30 | } 31 | 32 | #[cfg(feature = "e2e-tests")] 33 | async fn query_values_in_test(cql: &str, session: CurrentSession) { 34 | session.query(cql).await.expect("create table error"); 35 | 36 | let query_insert = "INSERT INTO cdrs_test.test_query_values_in \ 37 | (id) VALUES (?)"; 38 | 39 | let items = vec!["1".to_string(), "2".to_string(), "3".to_string()]; 40 | 41 | for item in items { 42 | let values = query_values!(item); 43 | session 44 | .query_with_values(query_insert, values) 45 | .await 46 | .expect("insert item error"); 47 | } 48 | 49 | let cql = "SELECT * FROM cdrs_test.test_query_values_in WHERE id IN ?"; 50 | let criteria = vec!["1".to_string(), "3".to_string()]; 51 | 52 | let rows = session 53 | .query_with_values(cql, query_values!(criteria.clone())) 54 | .await 55 | .expect("select values query error") 56 | .response_body() 57 | .expect("get body error") 58 | .into_rows() 59 | .expect("converting into rows error"); 60 | 61 | assert_eq!(rows.len(), criteria.len()); 62 | 63 | let found_all_matching_criteria = criteria.iter().all(|criteria_item: &String| { 64 | rows.iter().any(|row| { 65 | let id: String = row.get_r_by_name("id").expect("id"); 66 | 67 | criteria_item.clone() == id 68 | }) 69 | }); 70 | 71 | assert!( 72 | found_all_matching_criteria, 73 | "should find at least one element for each criteria" 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /cdrs-tokio/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **cdrs** is a native Cassandra DB client written in Rust. 2 | //! 3 | //! ## Getting started 4 | //! 5 | //! This example configures a cluster consisting of a single node, and uses round-robin load balancing. 6 | //! 7 | //! ```no_run 8 | //! use cdrs_tokio::cluster::session::{TcpSessionBuilder, SessionBuilder}; 9 | //! use cdrs_tokio::cluster::NodeTcpConfigBuilder; 10 | //! use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 11 | //! use std::sync::Arc; 12 | //! 13 | //! #[tokio::main] 14 | //! async fn main() { 15 | //! let cluster_config = NodeTcpConfigBuilder::new() 16 | //! .with_contact_point("127.0.0.1:9042".into()) 17 | //! .build() 18 | //! .await 19 | //! .unwrap(); 20 | //! let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 21 | //! .build() 22 | //! .await 23 | //! .unwrap(); 24 | //! 25 | //! let create_ks = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 26 | //! 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 27 | //! session 28 | //! .query(create_ks) 29 | //! .await 30 | //! .expect("Keyspace create error"); 31 | //! } 32 | //! ``` 33 | //! 34 | //! ## Nodes and load balancing 35 | //! 36 | //! In order to maximize efficiency, the driver needs to be appropriately configured for given use 37 | //! case. Please look at available [load balancers](crate::load_balancing) and 38 | //! [node distance evaluators](crate::load_balancing::node_distance_evaluator) to pick the optimal 39 | //! solution when building the [`Session`](crate::cluster::session::Session). Topology-aware load 40 | //! balancing is preferred when dealing with multi-node clusters, otherwise simpler strategies might 41 | //! prove more efficient. 42 | 43 | #[macro_use] 44 | mod macros; 45 | 46 | pub mod cluster; 47 | pub mod envelope_parser; 48 | pub mod load_balancing; 49 | 50 | pub mod frame_encoding; 51 | pub mod future; 52 | pub mod retry; 53 | pub mod speculative_execution; 54 | pub mod statement; 55 | pub mod transport; 56 | 57 | pub use cassandra_protocol::authenticators; 58 | pub use cassandra_protocol::compression; 59 | pub use cassandra_protocol::consistency; 60 | pub use cassandra_protocol::error; 61 | pub use cassandra_protocol::frame; 62 | pub use cassandra_protocol::query; 63 | pub use cassandra_protocol::types; 64 | 65 | pub type Error = error::Error; 66 | pub type Result = error::Result; 67 | 68 | #[cfg(feature = "derive")] 69 | pub use cdrs_tokio_helpers_derive::{DbMirror, IntoCdrsValue, TryFromRow, TryFromUdt}; 70 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_register.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::events::SimpleServerEvent; 3 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 4 | use crate::types::{from_cursor_string_list, serialize_str_list}; 5 | use derive_more::Constructor; 6 | use itertools::Itertools; 7 | use std::convert::TryFrom; 8 | use std::io::Cursor; 9 | 10 | /// The structure which represents a body of a envelope of type `register`. 11 | #[derive(Debug, Constructor, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Clone)] 12 | pub struct BodyReqRegister { 13 | pub events: Vec, 14 | } 15 | 16 | impl Serialize for BodyReqRegister { 17 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 18 | let events = self.events.iter().map(|event| event.as_str()); 19 | serialize_str_list(cursor, events, version); 20 | } 21 | } 22 | 23 | impl FromCursor for BodyReqRegister { 24 | fn from_cursor(cursor: &mut Cursor<&[u8]>, _version: Version) -> error::Result { 25 | let events = from_cursor_string_list(cursor)?; 26 | events 27 | .iter() 28 | .map(|event| SimpleServerEvent::try_from(event.as_str())) 29 | .try_collect() 30 | .map(BodyReqRegister::new) 31 | } 32 | } 33 | 34 | impl Envelope { 35 | /// Creates new envelope of type `REGISTER`. 36 | pub fn new_req_register(events: Vec, version: Version) -> Envelope { 37 | let direction = Direction::Request; 38 | let opcode = Opcode::Register; 39 | let register_body = BodyReqRegister::new(events); 40 | 41 | Envelope::new( 42 | version, 43 | direction, 44 | Flags::empty(), 45 | opcode, 46 | 0, 47 | register_body.serialize_to_vec(version), 48 | None, 49 | vec![], 50 | ) 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use crate::events::SimpleServerEvent; 57 | use crate::frame::message_register::BodyReqRegister; 58 | use crate::frame::{FromCursor, Version}; 59 | use std::io::Cursor; 60 | 61 | #[test] 62 | fn should_deserialize_body() { 63 | let data = [ 64 | 0, 1, 0, 15, 0x54, 0x4f, 0x50, 0x4f, 0x4c, 0x4f, 0x47, 0x59, 0x5f, 0x43, 0x48, 0x41, 65 | 0x4e, 0x47, 0x45, 66 | ]; 67 | let mut cursor = Cursor::new(data.as_slice()); 68 | 69 | let body = BodyReqRegister::from_cursor(&mut cursor, Version::V4).unwrap(); 70 | assert_eq!(body.events, vec![SimpleServerEvent::TopologyChange]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/tuple.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use num_bigint::BigInt; 3 | use std::hash::{Hash, Hasher}; 4 | use std::net::IpAddr; 5 | use time::PrimitiveDateTime; 6 | use uuid::Uuid; 7 | 8 | use crate::error::{column_is_empty_err, Error, Result}; 9 | use crate::frame::message_result::{CTuple, ColType, ColTypeOption, ColTypeOptionValue}; 10 | use crate::frame::Version; 11 | use crate::types::blob::Blob; 12 | use crate::types::data_serialization_types::*; 13 | use crate::types::decimal::Decimal; 14 | use crate::types::list::List; 15 | use crate::types::map::Map; 16 | use crate::types::udt::Udt; 17 | use crate::types::{ByIndex, CBytes, IntoRustByIndex}; 18 | 19 | #[derive(Debug)] 20 | pub struct Tuple { 21 | data: Vec<(ColTypeOption, CBytes)>, 22 | protocol_version: Version, 23 | } 24 | 25 | impl PartialEq for Tuple { 26 | fn eq(&self, other: &Tuple) -> bool { 27 | if self.data.len() != other.data.len() { 28 | return false; 29 | } 30 | for (s, o) in self.data.iter().zip(other.data.iter()) { 31 | if s.1 != o.1 { 32 | return false; 33 | } 34 | } 35 | true 36 | } 37 | } 38 | 39 | impl Eq for Tuple {} 40 | 41 | impl Hash for Tuple { 42 | fn hash(&self, state: &mut H) { 43 | for data in &self.data { 44 | data.1.hash(state); 45 | } 46 | } 47 | } 48 | 49 | impl Tuple { 50 | pub fn new(elements: Vec, metadata: &CTuple, protocol_version: Version) -> Tuple { 51 | Tuple { 52 | data: metadata 53 | .types 54 | .iter() 55 | .zip(elements) 56 | .map(|(val_type, val_b)| (val_type.clone(), val_b)) 57 | .collect(), 58 | protocol_version, 59 | } 60 | } 61 | } 62 | 63 | impl ByIndex for Tuple {} 64 | 65 | into_rust_by_index!(Tuple, Blob); 66 | into_rust_by_index!(Tuple, String); 67 | into_rust_by_index!(Tuple, bool); 68 | into_rust_by_index!(Tuple, i64); 69 | into_rust_by_index!(Tuple, i32); 70 | into_rust_by_index!(Tuple, i16); 71 | into_rust_by_index!(Tuple, i8); 72 | into_rust_by_index!(Tuple, f64); 73 | into_rust_by_index!(Tuple, f32); 74 | into_rust_by_index!(Tuple, IpAddr); 75 | into_rust_by_index!(Tuple, Uuid); 76 | into_rust_by_index!(Tuple, List); 77 | into_rust_by_index!(Tuple, Map); 78 | into_rust_by_index!(Tuple, Udt); 79 | into_rust_by_index!(Tuple, Tuple); 80 | into_rust_by_index!(Tuple, PrimitiveDateTime); 81 | into_rust_by_index!(Tuple, Decimal); 82 | into_rust_by_index!(Tuple, NaiveDateTime); 83 | into_rust_by_index!(Tuple, DateTime); 84 | into_rust_by_index!(Tuple, BigInt); 85 | 86 | tuple_as_cassandra_type!(); 87 | -------------------------------------------------------------------------------- /cdrs-tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cdrs-tokio" 3 | version = "9.0.0" 4 | authors = ["Alex Pikalov ", "Kamil Rojewski "] 5 | edition = "2018" 6 | description = "Async Cassandra DB driver written in Rust" 7 | documentation = "https://docs.rs/cdrs-tokio" 8 | homepage = "https://github.com/krojew/cdrs-tokio" 9 | repository = "https://github.com/krojew/cdrs-tokio" 10 | readme = "../README.md" 11 | keywords = ["cassandra", "driver", "client", "cassandradb", "async"] 12 | license = "MIT/Apache-2.0" 13 | categories = ["asynchronous", "database"] 14 | rust-version = "1.80" 15 | 16 | [features] 17 | rust-tls = ["tokio-rustls", "webpki"] 18 | e2e-tests = [] 19 | derive = ["cdrs-tokio-helpers-derive"] 20 | http-proxy = ["async-http-proxy"] 21 | 22 | [dependencies] 23 | arc-swap.workspace = true 24 | atomic = "0.6.0" 25 | bytemuck = { version = "1.22.0", features = ["derive"] } 26 | cassandra-protocol = { path = "../cassandra-protocol", version = "4.0.0" } 27 | cdrs-tokio-helpers-derive = { path = "../cdrs-tokio-helpers-derive", version = "5.0.3", optional = true } 28 | derive_more.workspace = true 29 | derivative.workspace = true 30 | futures = { version = "0.3.28", default-features = false, features = ["alloc"] } 31 | fxhash = "0.2.1" 32 | itertools.workspace = true 33 | rand = "0.9.0" 34 | serde_json = "1.0.140" 35 | thiserror.workspace = true 36 | tokio = { version = "1.48.0", features = ["net", "io-util", "rt", "sync", "macros", "rt-multi-thread", "time"] } 37 | # note: default features for tokio-rustls include aws_lc_rs, which require clang on Windows => disable and let users 38 | # enable it explicitly 39 | tokio-rustls = { version = "0.26.0", optional = true, default-features = false, features = ["logging", "tls12"] } 40 | tracing = "0.1.41" 41 | uuid.workspace = true 42 | webpki = { version = "0.22.2", optional = true } 43 | 44 | [dependencies.async-http-proxy] 45 | version = "1.2.5" 46 | optional = true 47 | features = ["runtime-tokio", "basic-auth"] 48 | 49 | [dev-dependencies] 50 | float_eq = "1.0.1" 51 | maplit = "1.0.2" 52 | mockall = "0.14.0" 53 | regex = "1.11.1" 54 | uuid = { version = "1.19.0", features = ["v4"] } 55 | time = { version = "0.3.41", features = ["std", "macros"] } 56 | 57 | [[example]] 58 | name = "crud_operations" 59 | required-features = ["derive"] 60 | 61 | [[example]] 62 | name = "generic_connection" 63 | required-features = ["derive"] 64 | 65 | [[example]] 66 | name = "insert_collection" 67 | required-features = ["derive"] 68 | 69 | [[example]] 70 | name = "multiple_thread" 71 | required-features = ["derive"] 72 | 73 | [[example]] 74 | name = "paged_query" 75 | required-features = ["derive"] 76 | 77 | [[example]] 78 | name = "prepare_batch_execute" 79 | required-features = ["derive"] 80 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/paged_query.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "e2e-tests")] 2 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 9 | #[cfg(feature = "e2e-tests")] 10 | use cdrs_tokio::retry::NeverReconnectionPolicy; 11 | #[cfg(feature = "e2e-tests")] 12 | use std::sync::Arc; 13 | 14 | #[tokio::test] 15 | #[cfg(feature = "e2e-tests")] 16 | async fn paged_query() { 17 | let cluster_config = NodeTcpConfigBuilder::new() 18 | .with_contact_point("127.0.0.1:9042".into()) 19 | .with_authenticator_provider(Arc::new(NoneAuthenticatorProvider)) 20 | .build() 21 | .await 22 | .unwrap(); 23 | let lb = RoundRobinLoadBalancingStrategy::new(); 24 | let session = TcpSessionBuilder::new(lb, cluster_config) 25 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 26 | .build() 27 | .await 28 | .unwrap(); 29 | 30 | session 31 | .query( 32 | "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 33 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", 34 | ) 35 | .await 36 | .expect("Keyspace creation error"); 37 | 38 | session 39 | .query("use test_ks") 40 | .await 41 | .expect("Using keyspace went wrong"); 42 | 43 | session.query("create table if not exists user (user_id int primary key) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };").await.expect("Could not create table"); 44 | 45 | for i in 0..=9 { 46 | session 47 | .query(format!("insert into user(user_id) values ({i})")) 48 | .await 49 | .expect("Could not create table"); 50 | } 51 | 52 | let mut pager = session.paged(3); 53 | let mut query_pager = pager.query("SELECT * FROM user"); 54 | 55 | // This returns always false the first time 56 | assert!(!query_pager.has_more()); 57 | 58 | let rows = query_pager.next().await.expect("pager next"); 59 | assert_eq!(3, rows.len()); 60 | assert!(query_pager.has_more()); 61 | let rows = query_pager.next().await.expect("pager next"); 62 | assert_eq!(3, rows.len()); 63 | assert!(query_pager.has_more()); 64 | let rows = query_pager.next().await.expect("pager next"); 65 | assert_eq!(3, rows.len()); 66 | assert!(query_pager.has_more()); 67 | let rows = query_pager.next().await.expect("pager next"); 68 | assert_eq!(1, rows.len()); 69 | 70 | assert!(!query_pager.has_more()); 71 | } 72 | -------------------------------------------------------------------------------- /cdrs-tokio-helpers-derive/src/into_cdrs_value.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use proc_macro2::TokenStream; 3 | use quote::*; 4 | use syn::spanned::Spanned; 5 | use syn::{Data, DataStruct, DeriveInput, Error, Result}; 6 | 7 | use crate::common::get_ident_string; 8 | 9 | pub fn impl_into_cdrs_value(ast: &DeriveInput) -> Result { 10 | let name = &ast.ident; 11 | if let Data::Struct(DataStruct { ref fields, .. }) = ast.data { 12 | let convert_into_bytes: Vec<_> = fields.iter().map(|field| { 13 | let field_ident = field.ident.clone().ok_or_else(|| Error::new(field.span(), "IntoCdrsValue requires all fields be named!"))?; 14 | get_ident_string(&field.ty, &field_ident.to_string()).map(|ident| { 15 | if ident == "Option" { 16 | // We are assuming here primitive value serialization will not change across protocol 17 | // versions, which gives us simpler user API. 18 | quote! { 19 | match value.#field_ident { 20 | Some(ref val) => { 21 | let field_bytes: Self = val.clone().into(); 22 | cdrs_tokio::types::value::Value::new(field_bytes).serialize(&mut cursor, cdrs_tokio::frame::Version::V4); 23 | }, 24 | None => { 25 | cdrs_tokio::types::value::Value::NotSet.serialize(&mut cursor, cdrs_tokio::frame::Version::V4); 26 | } 27 | } 28 | } 29 | } else { 30 | quote! { 31 | let field_bytes: Self = value.#field_ident.into(); 32 | cdrs_tokio::types::value::Value::new(field_bytes).serialize(&mut cursor, cdrs_tokio::frame::Version::V4); 33 | } 34 | } 35 | }) 36 | }).try_collect()?; 37 | // As Value has following implementation impl> From for Value 38 | // for a struct it's enough to implement Into in order to be convertible into Value 39 | // which is used for making queries 40 | Ok(quote! { 41 | #[automatically_derived] 42 | impl From<#name> for cdrs_tokio::types::value::Bytes { 43 | fn from(value: #name) -> Self { 44 | use cdrs_tokio::frame::Serialize; 45 | 46 | let mut bytes: Vec = Vec::new(); 47 | let mut cursor = std::io::Cursor::new(&mut bytes); 48 | #(#convert_into_bytes)* 49 | Self::new(bytes) 50 | } 51 | } 52 | }) 53 | } else { 54 | Err(Error::new( 55 | ast.span(), 56 | "#[derive(IntoCdrsValue)] can only be defined for structs!", 57 | )) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_supported.rs: -------------------------------------------------------------------------------- 1 | use super::Serialize; 2 | use crate::error; 3 | use crate::frame::{FromCursor, Version}; 4 | use crate::types::{from_cursor_str, from_cursor_string_list, serialize_str, CIntShort, SHORT_LEN}; 5 | use std::collections::HashMap; 6 | use std::io::{Cursor, Read}; 7 | 8 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 9 | pub struct BodyResSupported { 10 | pub data: HashMap>, 11 | } 12 | 13 | impl Serialize for BodyResSupported { 14 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 15 | (self.data.len() as CIntShort).serialize(cursor, version); 16 | self.data.iter().for_each(|(key, value)| { 17 | serialize_str(cursor, key.as_str(), version); 18 | (value.len() as CIntShort).serialize(cursor, version); 19 | value 20 | .iter() 21 | .for_each(|s| serialize_str(cursor, s.as_str(), version)); 22 | }) 23 | } 24 | } 25 | 26 | impl FromCursor for BodyResSupported { 27 | fn from_cursor( 28 | cursor: &mut Cursor<&[u8]>, 29 | _version: Version, 30 | ) -> error::Result { 31 | let mut buff = [0; SHORT_LEN]; 32 | cursor.read_exact(&mut buff)?; 33 | 34 | let l = i16::from_be_bytes(buff) as usize; 35 | let mut data: HashMap> = HashMap::with_capacity(l); 36 | for _ in 0..l { 37 | let name = from_cursor_str(cursor)?.to_string(); 38 | let val = from_cursor_string_list(cursor)?; 39 | data.insert(name, val); 40 | } 41 | 42 | Ok(BodyResSupported { data }) 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | use crate::frame::traits::FromCursor; 50 | use crate::frame::Version; 51 | use std::io::Cursor; 52 | 53 | #[test] 54 | fn body_res_supported() { 55 | let bytes = [ 56 | 0, 1, // n options 57 | // 1-st option 58 | 0, 2, 97, 98, // key [string] "ab" 59 | 0, 2, 0, 1, 97, 0, 1, 98, /* value ["a", "b"] */ 60 | ]; 61 | let mut data: HashMap> = HashMap::new(); 62 | data.insert("ab".into(), vec!["a".into(), "b".into()]); 63 | let expected = BodyResSupported { data }; 64 | 65 | { 66 | let mut cursor: Cursor<&[u8]> = Cursor::new(&bytes); 67 | let auth = BodyResSupported::from_cursor(&mut cursor, Version::V4).unwrap(); 68 | assert_eq!(auth, expected); 69 | } 70 | 71 | { 72 | let mut buffer = Vec::new(); 73 | let mut cursor = Cursor::new(&mut buffer); 74 | expected.serialize(&mut cursor, Version::V4); 75 | assert_eq!(buffer, bytes); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/query_values.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use std::collections::HashMap; 3 | use std::io::Cursor; 4 | 5 | use crate::frame::{Serialize, Version}; 6 | use crate::types::serialize_str; 7 | use crate::types::value::Value; 8 | 9 | /// Enum that represents two types of query values: 10 | /// * values without name 11 | /// * values with names 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub enum QueryValues { 14 | SimpleValues(Vec), 15 | NamedValues(HashMap), 16 | } 17 | 18 | impl QueryValues { 19 | /// Returns `true` if query values is with names and `false` otherwise. 20 | #[inline] 21 | pub fn has_names(&self) -> bool { 22 | !matches!(*self, QueryValues::SimpleValues(_)) 23 | } 24 | 25 | /// Returns the number of values. 26 | pub fn len(&self) -> usize { 27 | match *self { 28 | QueryValues::SimpleValues(ref v) => v.len(), 29 | QueryValues::NamedValues(ref m) => m.len(), 30 | } 31 | } 32 | 33 | #[inline] 34 | pub fn is_empty(&self) -> bool { 35 | self.len() == 0 36 | } 37 | } 38 | 39 | impl> From> for QueryValues { 40 | /// Converts values from `Vec` to query values without names `QueryValues::SimpleValues`. 41 | fn from(values: Vec) -> QueryValues { 42 | let vals = values.into_iter().map_into(); 43 | QueryValues::SimpleValues(vals.collect()) 44 | } 45 | } 46 | 47 | impl + Clone> From<&[T]> for QueryValues { 48 | /// Converts values from `Vec` to query values without names `QueryValues::SimpleValues`. 49 | fn from(values: &[T]) -> QueryValues { 50 | let values = values.iter().map(|v| v.clone().into()); 51 | QueryValues::SimpleValues(values.collect()) 52 | } 53 | } 54 | 55 | impl> From> for QueryValues { 56 | /// Converts values from `HashMap` to query values with names `QueryValues::NamedValues`. 57 | fn from(values: HashMap) -> QueryValues { 58 | let mut map = HashMap::with_capacity(values.len()); 59 | for (name, val) in values { 60 | map.insert(name.to_string(), val.into()); 61 | } 62 | 63 | QueryValues::NamedValues(map) 64 | } 65 | } 66 | 67 | impl Serialize for QueryValues { 68 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 69 | match self { 70 | QueryValues::SimpleValues(v) => { 71 | for value in v { 72 | value.serialize(cursor, version); 73 | } 74 | } 75 | QueryValues::NamedValues(v) => { 76 | for (key, value) in v { 77 | serialize_str(cursor, key, version); 78 | value.serialize(cursor, version); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use self::cluster_metadata_manager::ClusterMetadataManager; 2 | #[cfg(feature = "http-proxy")] 3 | pub use self::config_proxy::{HttpProxyConfig, HttpProxyConfigBuilder}; 4 | #[cfg(feature = "rust-tls")] 5 | pub use self::config_rustls::{NodeRustlsConfig, NodeRustlsConfigBuilder}; 6 | pub use self::config_tcp::{NodeTcpConfig, NodeTcpConfigBuilder}; 7 | pub use self::connection_manager::{startup, ConnectionManager}; 8 | pub use self::keyspace_holder::KeyspaceHolder; 9 | pub use self::node_address::NodeAddress; 10 | pub use self::node_info::NodeInfo; 11 | pub use self::pager::{ExecPager, PagerState, QueryPager, SessionPager}; 12 | #[cfg(feature = "rust-tls")] 13 | pub use self::rustls_connection_manager::RustlsConnectionManager; 14 | pub use self::session::connect_generic; 15 | pub(crate) use self::session_context::SessionContext; 16 | pub use self::tcp_connection_manager::TcpConnectionManager; 17 | pub use self::token_map::TokenMap; 18 | pub use self::topology::cluster_metadata::ClusterMetadata; 19 | use crate::cluster::connection_pool::ConnectionPoolConfig; 20 | use crate::future::BoxFuture; 21 | use crate::transport::CdrsTransport; 22 | use cassandra_protocol::error; 23 | use cassandra_protocol::frame::Version; 24 | pub use cassandra_protocol::token::Murmur3Token; 25 | use std::sync::Arc; 26 | 27 | mod cluster_metadata_manager; 28 | #[cfg(feature = "http-proxy")] 29 | mod config_proxy; 30 | #[cfg(feature = "rust-tls")] 31 | mod config_rustls; 32 | mod config_tcp; 33 | #[cfg(not(test))] 34 | mod connection_manager; 35 | #[cfg(test)] 36 | pub mod connection_manager; 37 | pub mod connection_pool; 38 | mod control_connection; 39 | mod keyspace_holder; 40 | mod metadata_builder; 41 | mod node_address; 42 | mod node_info; 43 | mod pager; 44 | #[cfg(feature = "rust-tls")] 45 | mod rustls_connection_manager; 46 | pub mod send_envelope; 47 | pub mod session; 48 | mod session_context; 49 | mod tcp_connection_manager; 50 | mod token_map; 51 | pub mod topology; 52 | 53 | /// Generic connection configuration trait that can be used to create user-supplied 54 | /// connection objects that can be used with the `session::connect()` function. 55 | pub trait GenericClusterConfig>: Send + Sync { 56 | fn create_manager( 57 | &self, 58 | keyspace_holder: Arc, 59 | ) -> BoxFuture<'_, error::Result>; 60 | 61 | /// Returns desired event channel capacity. Take a look at 62 | /// [`Session`](session::Session) builders for more info. 63 | fn event_channel_capacity(&self) -> usize; 64 | 65 | /// Cassandra protocol version to use. 66 | fn version(&self) -> Version; 67 | 68 | /// Connection pool configuration. 69 | fn connection_pool_config(&self) -> ConnectionPoolConfig; 70 | 71 | /// Enable beta protocol support. 72 | fn beta_protocol(&self) -> bool { 73 | false 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/duration.rs: -------------------------------------------------------------------------------- 1 | use integer_encoding::VarInt; 2 | use std::io::{Cursor, Write}; 3 | use thiserror::Error; 4 | 5 | use crate::frame::{Serialize, Version}; 6 | 7 | /// Possible `Duration` creation error. 8 | #[derive(Debug, Error, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 9 | pub enum DurationCreationError { 10 | #[error( 11 | "All values must be either negative or positive, got {months} months, {days} days, {nanoseconds} nanoseconds" 12 | )] 13 | MixedPositiveAndNegative { 14 | months: i32, 15 | days: i32, 16 | nanoseconds: i64, 17 | }, 18 | } 19 | 20 | /// Cassandra Duration type. A duration stores separately months, days, and seconds due to the fact 21 | /// that the number of days in a month varies, and a day can have 23 or 25 hours if a daylight 22 | /// saving is involved. 23 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 24 | pub struct Duration { 25 | months: i32, 26 | days: i32, 27 | nanoseconds: i64, 28 | } 29 | 30 | impl Duration { 31 | pub fn new(months: i32, days: i32, nanoseconds: i64) -> Result { 32 | if (months < 0 || days < 0 || nanoseconds < 0) 33 | && (months > 0 || days > 0 || nanoseconds > 0) 34 | { 35 | Err(DurationCreationError::MixedPositiveAndNegative { 36 | months, 37 | days, 38 | nanoseconds, 39 | }) 40 | } else { 41 | Ok(Self { 42 | months, 43 | days, 44 | nanoseconds, 45 | }) 46 | } 47 | } 48 | 49 | pub fn months(&self) -> i32 { 50 | self.months 51 | } 52 | 53 | pub fn days(&self) -> i32 { 54 | self.days 55 | } 56 | 57 | pub fn nanoseconds(&self) -> i64 { 58 | self.nanoseconds 59 | } 60 | } 61 | 62 | impl Serialize for Duration { 63 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 64 | let month_space = self.months.required_space(); 65 | let day_space = self.days.required_space(); 66 | 67 | let mut buffer = vec![0u8; month_space + day_space + self.nanoseconds.required_space()]; 68 | 69 | self.months.encode_var(&mut buffer); 70 | self.days.encode_var(&mut buffer[month_space..]); 71 | self.nanoseconds 72 | .encode_var(&mut buffer[(month_space + day_space)..]); 73 | 74 | let _ = cursor.write(&buffer); 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::frame::{Serialize, Version}; 81 | use crate::types::duration::Duration; 82 | 83 | #[test] 84 | fn should_serialize_duration() { 85 | let duration = Duration::new(100, 200, 300).unwrap(); 86 | assert_eq!( 87 | duration.serialize_to_vec(Version::V5), 88 | vec![200, 1, 144, 3, 216, 4] 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /documentation/query-values.md: -------------------------------------------------------------------------------- 1 | # Query `Value` 2 | 3 | Query `Value`-s can be used along with query string templates. Query string templates is a special sort of query string that contains `?` sign. `?` will be substituted by CDRS driver with query `Values`. 4 | 5 | For instance: 6 | 7 | ```rust 8 | const INSERT_NUMBERS_QUERY: &'static str = "INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)"; 9 | let values = query_values!(1 as i32, 1 as i64); 10 | 11 | session.query_with_values(INSERT_NUMBERS_QUERY, values).await.unwrap(); 12 | ``` 13 | 14 | `INSERT_NUMBERS_QUERY` is a typical query template. `session::query_with_values` method provides an API for using such query strings along with query values. 15 | 16 | There is full list of `Session` methods that allow using values and query templates: 17 | 18 | - `exec_with_values` - executes previously prepared query with provided values (see [example](../examples/prepare_batch_execute.rs) and/or [Preparing and Executing](./preparing-and-executing-queries.md) section); 19 | 20 | - `query_with_params_tw` - immediately executes a query using provided values (see [example](../examples/crud_operations.rs)) 21 | 22 | ## Simple `Value` and `Value` with names 23 | 24 | There are two type of query values supported by CDRS: 25 | 26 | - simple `Value`-s may be imagined as a tuple of actual values. This values will be inserted instead of a `?` that has the same index number as a `Value` within a tuple. To easily create `Value`-s CDRS provides `query_values!` macro: 27 | 28 | ```rust 29 | let values = query_values!(1 as i32, 1 as i64); 30 | ``` 31 | 32 | - `Value`-s with names may be imagined as a `Map` that links a table column name with a value that should be inserted in a column. It means that `Value`-s with maps should not necessarily have the same order as a corresponded `?` in a query template: 33 | 34 | ```rust 35 | const INSERT_NUMBERS_QUERY: &'static str = "INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)"; 36 | let values = query_values!(my_bigint => 1 as i64, my_int => 1 as i64); 37 | 38 | session.query_with_values(INSERT_NUMBERS_QUERY, values).await.unwrap(); 39 | ``` 40 | 41 | What kind of values can be used as `query_values!` arguments? All types that have implementations of `Into`. 42 | 43 | For Rust structs represented by [Cassandra User Defined types](http://cassandra.apache.org/doc/4.0/cql/types.html#grammar-token-user_defined_type) `#[derive(IntoCdrsValue)]` can be used for recursive implementation. See [CRUD example](../examples/crud_operations.rs). 44 | 45 | ### Reference 46 | 47 | 1. Cassandra official docs - User Defined Types http://cassandra.apache.org/doc/4.0/cql/types.html#grammar-token-user_defined_type. 48 | 49 | 2. Datastax - User Defined Types https://docs.datastax.com/en/cql/3.3/cql/cql_using/useCreateUDT.html. 50 | 51 | 3. ScyllaDB - User Defined Types https://docs.scylladb.com/getting-started/types/ 52 | 53 | 4. [CDRS CRUD Example](../examples/crud_operations.rs) 54 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::Version; 3 | use crate::query; 4 | use num_bigint::BigInt; 5 | use std::io::{Cursor, Write}; 6 | 7 | /// Trait that should be implemented by all types that wish to be serialized to a buffer. 8 | pub trait Serialize { 9 | /// Serializes given value using the cursor. 10 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version); 11 | 12 | /// Wrapper for easily starting hierarchical serialization. 13 | fn serialize_to_vec(&self, version: Version) -> Vec { 14 | let mut buf = vec![]; 15 | 16 | self.serialize(&mut Cursor::new(&mut buf), version); 17 | buf 18 | } 19 | } 20 | 21 | /// `FromBytes` should be used to parse an array of bytes into a structure. 22 | pub trait FromBytes { 23 | /// It gets and array of bytes and should return an implementor struct. 24 | fn from_bytes(bytes: &[u8]) -> error::Result 25 | where 26 | Self: Sized; 27 | } 28 | 29 | /// `FromCursor` should be used to get parsed structure from an `io:Cursor` 30 | /// which bound to an array of bytes. 31 | pub trait FromCursor { 32 | /// Tries to parse Self from a cursor of bytes. 33 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result 34 | where 35 | Self: Sized; 36 | } 37 | 38 | /// The trait that allows transformation of `Self` to CDRS query values. 39 | pub trait IntoQueryValues { 40 | fn into_query_values(self) -> query::QueryValues; 41 | } 42 | 43 | pub trait TryFromRow: Sized { 44 | fn try_from_row(row: crate::types::rows::Row) -> error::Result; 45 | } 46 | 47 | pub trait TryFromUdt: Sized { 48 | fn try_from_udt(udt: crate::types::udt::Udt) -> error::Result; 49 | } 50 | 51 | impl Serialize for [u8; S] { 52 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 53 | let _ = cursor.write(self); 54 | } 55 | } 56 | 57 | impl Serialize for &[u8] { 58 | #[inline] 59 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 60 | let _ = cursor.write(self); 61 | } 62 | } 63 | 64 | impl Serialize for Vec { 65 | #[inline] 66 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 67 | let _ = cursor.write(self); 68 | } 69 | } 70 | 71 | impl Serialize for BigInt { 72 | #[inline] 73 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 74 | let _ = cursor.write(&self.to_signed_bytes_be()); 75 | } 76 | } 77 | 78 | macro_rules! impl_serialized { 79 | ($t:ty) => { 80 | impl Serialize for $t { 81 | #[inline] 82 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, _version: Version) { 83 | let _ = cursor.write(&self.to_be_bytes()); 84 | } 85 | } 86 | }; 87 | } 88 | 89 | impl_serialized!(i8); 90 | impl_serialized!(i16); 91 | impl_serialized!(i32); 92 | impl_serialized!(i64); 93 | impl_serialized!(u8); 94 | impl_serialized!(u16); 95 | impl_serialized!(u32); 96 | impl_serialized!(u64); 97 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/from_cdrs.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use std::num::{NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8}; 3 | 4 | use chrono::prelude::*; 5 | use time::PrimitiveDateTime; 6 | use uuid::Uuid; 7 | 8 | use crate::error::Result as CdrsResult; 9 | use crate::types::blob::Blob; 10 | use crate::types::decimal::Decimal; 11 | use crate::types::list::List; 12 | use crate::types::map::Map; 13 | use crate::types::tuple::Tuple; 14 | use crate::types::udt::Udt; 15 | use crate::types::{AsRustType, ByName, IntoRustByName}; 16 | 17 | pub trait FromCdrs { 18 | fn from_cdrs(cdrs_type: T) -> CdrsResult> 19 | where 20 | Self: Sized, 21 | T: AsRustType, 22 | { 23 | cdrs_type.as_rust_type() 24 | } 25 | 26 | fn from_cdrs_r(cdrs_type: T) -> CdrsResult 27 | where 28 | Self: Sized, 29 | T: AsRustType, 30 | { 31 | cdrs_type.as_r_type() 32 | } 33 | } 34 | 35 | impl FromCdrs for Blob {} 36 | impl FromCdrs for String {} 37 | impl FromCdrs for bool {} 38 | impl FromCdrs for i64 {} 39 | impl FromCdrs for i32 {} 40 | impl FromCdrs for i16 {} 41 | impl FromCdrs for i8 {} 42 | impl FromCdrs for f64 {} 43 | impl FromCdrs for f32 {} 44 | impl FromCdrs for IpAddr {} 45 | impl FromCdrs for Uuid {} 46 | impl FromCdrs for List {} 47 | impl FromCdrs for Map {} 48 | impl FromCdrs for Udt {} 49 | impl FromCdrs for Tuple {} 50 | impl FromCdrs for PrimitiveDateTime {} 51 | impl FromCdrs for Decimal {} 52 | impl FromCdrs for NonZeroI8 {} 53 | impl FromCdrs for NonZeroI16 {} 54 | impl FromCdrs for NonZeroI32 {} 55 | impl FromCdrs for NonZeroI64 {} 56 | impl FromCdrs for NaiveDateTime {} 57 | impl FromCdrs for DateTime {} 58 | 59 | pub trait FromCdrsByName { 60 | fn from_cdrs_by_name(cdrs_type: &T, name: &str) -> CdrsResult> 61 | where 62 | Self: Sized, 63 | T: ByName + IntoRustByName, 64 | { 65 | cdrs_type.by_name(name) 66 | } 67 | 68 | fn from_cdrs_r(cdrs_type: &T, name: &str) -> CdrsResult 69 | where 70 | Self: Sized, 71 | T: ByName + IntoRustByName + ::std::fmt::Debug, 72 | { 73 | cdrs_type.r_by_name(name) 74 | } 75 | } 76 | 77 | impl FromCdrsByName for Blob {} 78 | impl FromCdrsByName for String {} 79 | impl FromCdrsByName for bool {} 80 | impl FromCdrsByName for i64 {} 81 | impl FromCdrsByName for i32 {} 82 | impl FromCdrsByName for i16 {} 83 | impl FromCdrsByName for i8 {} 84 | impl FromCdrsByName for f64 {} 85 | impl FromCdrsByName for f32 {} 86 | impl FromCdrsByName for IpAddr {} 87 | impl FromCdrsByName for Uuid {} 88 | impl FromCdrsByName for List {} 89 | impl FromCdrsByName for Map {} 90 | impl FromCdrsByName for Udt {} 91 | impl FromCdrsByName for Tuple {} 92 | impl FromCdrsByName for PrimitiveDateTime {} 93 | impl FromCdrsByName for Decimal {} 94 | impl FromCdrsByName for NonZeroI8 {} 95 | impl FromCdrsByName for NonZeroI16 {} 96 | impl FromCdrsByName for NonZeroI32 {} 97 | impl FromCdrsByName for NonZeroI64 {} 98 | impl FromCdrsByName for NaiveDateTime {} 99 | impl FromCdrsByName for DateTime {} 100 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/compression.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "e2e-tests")] 4 | use cassandra_protocol::compression::Compression; 5 | #[cfg(feature = "e2e-tests")] 6 | use cassandra_protocol::frame::Version; 7 | #[cfg(feature = "e2e-tests")] 8 | use cassandra_protocol::types::blob::Blob; 9 | #[cfg(feature = "e2e-tests")] 10 | use cassandra_protocol::types::ByIndex; 11 | #[cfg(feature = "e2e-tests")] 12 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 13 | #[cfg(feature = "e2e-tests")] 14 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 15 | #[cfg(feature = "e2e-tests")] 16 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 17 | #[cfg(feature = "e2e-tests")] 18 | use cdrs_tokio::query_values; 19 | #[cfg(feature = "e2e-tests")] 20 | use cdrs_tokio::retry::NeverReconnectionPolicy; 21 | #[cfg(feature = "e2e-tests")] 22 | use common::*; 23 | #[cfg(feature = "e2e-tests")] 24 | use rand::prelude::*; 25 | #[cfg(feature = "e2e-tests")] 26 | use rand::rng; 27 | #[cfg(feature = "e2e-tests")] 28 | use std::sync::Arc; 29 | 30 | #[cfg(feature = "e2e-tests")] 31 | async fn encode_decode_test(version: Version) { 32 | let cluster_config = NodeTcpConfigBuilder::new() 33 | .with_contact_point(ADDR.into()) 34 | .with_version(version) 35 | .build() 36 | .await 37 | .unwrap(); 38 | 39 | let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 40 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 41 | .with_compression(Compression::Lz4) 42 | .build() 43 | .await 44 | .unwrap(); 45 | 46 | session 47 | .query( 48 | "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 49 | replication = {'class': 'SimpleStrategy', 'replication_factor': 1} \ 50 | AND durable_writes = false", 51 | ) 52 | .await 53 | .unwrap(); 54 | 55 | session 56 | .query( 57 | "CREATE TABLE IF NOT EXISTS cdrs_test.test_compression (pk int PRIMARY KEY, data blob)", 58 | ) 59 | .await 60 | .unwrap(); 61 | 62 | let mut rng = rng(); 63 | 64 | let mut data = vec![0u8; 5 * 1024 * 1024]; 65 | for elem in &mut data { 66 | *elem = rng.random(); 67 | } 68 | 69 | let blob = Blob::new(data); 70 | 71 | session 72 | .query_with_values( 73 | "INSERT INTO cdrs_test.test_compression (pk, data) VALUES (1, ?)", 74 | query_values!(blob.clone()), 75 | ) 76 | .await 77 | .unwrap(); 78 | 79 | let stored_data: Blob = session 80 | .query("SELECT data FROM cdrs_test.test_compression WHERE pk = 1") 81 | .await 82 | .unwrap() 83 | .response_body() 84 | .unwrap() 85 | .into_rows() 86 | .unwrap() 87 | .first() 88 | .unwrap() 89 | .r_by_index::(0) 90 | .unwrap(); 91 | 92 | assert_eq!(stored_data, blob); 93 | } 94 | 95 | #[tokio::test] 96 | #[cfg(feature = "e2e-tests")] 97 | async fn encode_decode_test_v4() { 98 | encode_decode_test(Version::V4).await; 99 | } 100 | 101 | #[tokio::test] 102 | #[cfg(feature = "e2e-tests")] 103 | async fn encode_decode_test_v5() { 104 | encode_decode_test(Version::V5).await; 105 | } 106 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/common.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "e2e-tests")] 2 | use std::sync::Arc; 3 | 4 | #[cfg(feature = "e2e-tests")] 5 | use cassandra_protocol::frame::Version; 6 | #[cfg(feature = "e2e-tests")] 7 | use cdrs_tokio::cluster::session::Session; 8 | #[cfg(feature = "e2e-tests")] 9 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 10 | #[cfg(feature = "e2e-tests")] 11 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 12 | #[cfg(feature = "e2e-tests")] 13 | use cdrs_tokio::cluster::TcpConnectionManager; 14 | #[cfg(feature = "e2e-tests")] 15 | use cdrs_tokio::error::Result; 16 | #[cfg(feature = "e2e-tests")] 17 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 18 | #[cfg(feature = "e2e-tests")] 19 | use cdrs_tokio::retry::NeverReconnectionPolicy; 20 | #[cfg(feature = "e2e-tests")] 21 | use cdrs_tokio::transport::TransportTcp; 22 | #[cfg(feature = "e2e-tests")] 23 | use regex::Regex; 24 | 25 | #[cfg(feature = "e2e-tests")] 26 | pub const ADDR: &str = "127.0.0.1:9042"; 27 | 28 | #[cfg(feature = "e2e-tests")] 29 | pub type CurrentSession = Session< 30 | TransportTcp, 31 | TcpConnectionManager, 32 | RoundRobinLoadBalancingStrategy, 33 | >; 34 | 35 | #[cfg(feature = "e2e-tests")] 36 | #[allow(dead_code)] 37 | pub async fn setup(create_table_cql: &'static str, version: Version) -> Result { 38 | setup_multiple(&[create_table_cql], version).await 39 | } 40 | 41 | #[cfg(feature = "e2e-tests")] 42 | pub async fn setup_multiple( 43 | create_cqls: &[&'static str], 44 | version: Version, 45 | ) -> Result { 46 | let cluster_config = NodeTcpConfigBuilder::new() 47 | .with_contact_point(ADDR.into()) 48 | .with_version(version) 49 | .build() 50 | .await 51 | .unwrap(); 52 | let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 53 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 54 | .build() 55 | .await 56 | .unwrap(); 57 | let re_table_name = Regex::new(r"CREATE TABLE IF NOT EXISTS (\w+\.\w+)").unwrap(); 58 | 59 | let create_keyspace_query = "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 60 | replication = {'class': 'SimpleStrategy', 'replication_factor': 1} \ 61 | AND durable_writes = false"; 62 | session.query(create_keyspace_query).await?; 63 | 64 | for create_cql in create_cqls.iter() { 65 | let table_name = re_table_name 66 | .captures(create_cql) 67 | .map(|cap| cap.get(1).unwrap().as_str()); 68 | 69 | // Re-using tables is a lot faster than creating/dropping them for every test. 70 | // But if table definitions change while editing tests 71 | // the old tables need to be dropped. For example by uncommenting the following lines. 72 | // if let Some(table_name) = table_name { 73 | // let cql = format!("DROP TABLE IF EXISTS {}", table_name); 74 | // let query = QueryBuilder::new(cql).finalize(); 75 | // session.query(query, true, true)?; 76 | // } 77 | 78 | session.query(create_cql.to_owned()).await?; 79 | 80 | if let Some(table_name) = table_name { 81 | let cql = format!("TRUNCATE TABLE {table_name}"); 82 | session.query(cql).await?; 83 | } 84 | } 85 | 86 | Ok(session) 87 | } 88 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_request.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::frame::message_auth_response::BodyReqAuthResponse; 4 | use crate::frame::message_batch::BodyReqBatch; 5 | use crate::frame::message_execute::BodyReqExecuteOwned; 6 | use crate::frame::message_options::BodyReqOptions; 7 | use crate::frame::message_prepare::BodyReqPrepare; 8 | use crate::frame::message_query::BodyReqQuery; 9 | use crate::frame::message_register::BodyReqRegister; 10 | use crate::frame::message_startup::BodyReqStartup; 11 | use crate::frame::{FromCursor, Opcode, Serialize, Version}; 12 | use crate::{error, Error}; 13 | 14 | #[derive(Debug, PartialEq, Eq, Clone)] 15 | #[allow(clippy::large_enum_variant)] 16 | #[non_exhaustive] 17 | pub enum RequestBody { 18 | Startup(BodyReqStartup), 19 | Options(BodyReqOptions), 20 | Query(BodyReqQuery), 21 | Prepare(BodyReqPrepare), 22 | Execute(BodyReqExecuteOwned), 23 | Register(BodyReqRegister), 24 | Batch(BodyReqBatch), 25 | AuthResponse(BodyReqAuthResponse), 26 | } 27 | 28 | impl Serialize for RequestBody { 29 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 30 | match self { 31 | RequestBody::Query(body) => body.serialize(cursor, version), 32 | RequestBody::Startup(body) => body.serialize(cursor, version), 33 | RequestBody::Options(body) => body.serialize(cursor, version), 34 | RequestBody::Prepare(body) => body.serialize(cursor, version), 35 | RequestBody::Execute(body) => body.serialize(cursor, version), 36 | RequestBody::Register(body) => body.serialize(cursor, version), 37 | RequestBody::Batch(body) => body.serialize(cursor, version), 38 | RequestBody::AuthResponse(body) => body.serialize(cursor, version), 39 | } 40 | } 41 | } 42 | 43 | impl RequestBody { 44 | pub fn try_from( 45 | bytes: &[u8], 46 | response_type: Opcode, 47 | version: Version, 48 | ) -> error::Result { 49 | let mut cursor: Cursor<&[u8]> = Cursor::new(bytes); 50 | match response_type { 51 | Opcode::Startup => { 52 | BodyReqStartup::from_cursor(&mut cursor, version).map(RequestBody::Startup) 53 | } 54 | Opcode::Options => { 55 | BodyReqOptions::from_cursor(&mut cursor, version).map(RequestBody::Options) 56 | } 57 | Opcode::Query => { 58 | BodyReqQuery::from_cursor(&mut cursor, version).map(RequestBody::Query) 59 | } 60 | Opcode::Prepare => { 61 | BodyReqPrepare::from_cursor(&mut cursor, version).map(RequestBody::Prepare) 62 | } 63 | Opcode::Execute => { 64 | BodyReqExecuteOwned::from_cursor(&mut cursor, version).map(RequestBody::Execute) 65 | } 66 | Opcode::Register => { 67 | BodyReqRegister::from_cursor(&mut cursor, version).map(RequestBody::Register) 68 | } 69 | Opcode::Batch => { 70 | BodyReqBatch::from_cursor(&mut cursor, version).map(RequestBody::Batch) 71 | } 72 | Opcode::AuthResponse => BodyReqAuthResponse::from_cursor(&mut cursor, version) 73 | .map(RequestBody::AuthResponse), 74 | _ => Err(Error::NonRequestOpcode(response_type)), 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/single_node_speculative_execution.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 9 | #[cfg(feature = "e2e-tests")] 10 | use common::*; 11 | #[cfg(feature = "e2e-tests")] 12 | use std::sync::Arc; 13 | #[cfg(feature = "e2e-tests")] 14 | use std::time::Duration; 15 | 16 | #[cfg(feature = "e2e-tests")] 17 | use cdrs_tokio::query_values; 18 | #[cfg(feature = "e2e-tests")] 19 | use cdrs_tokio::retry::NeverReconnectionPolicy; 20 | #[cfg(feature = "e2e-tests")] 21 | use cdrs_tokio::speculative_execution::ConstantSpeculativeExecutionPolicy; 22 | #[cfg(feature = "e2e-tests")] 23 | use cdrs_tokio::types::IntoRustByName; 24 | 25 | #[tokio::test] 26 | #[cfg(feature = "e2e-tests")] 27 | async fn single_node_speculative_execution() { 28 | let cluster_config = NodeTcpConfigBuilder::new() 29 | .with_contact_point(ADDR.into()) 30 | .build() 31 | .await 32 | .unwrap(); 33 | 34 | let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 35 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 36 | .with_speculative_execution_policy(Box::new(ConstantSpeculativeExecutionPolicy::new( 37 | 5, 38 | Duration::from_secs(1), 39 | ))) 40 | .build() 41 | .await 42 | .unwrap(); 43 | 44 | let create_keyspace_query = "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 45 | replication = {'class': 'SimpleStrategy', 'replication_factor': 1} \ 46 | AND durable_writes = false"; 47 | session 48 | .query(create_keyspace_query) 49 | .await 50 | .expect("create keyspace error"); 51 | 52 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.single_node_speculative_execution \ 53 | (id text PRIMARY KEY)"; 54 | 55 | session.query(cql).await.expect("create table error"); 56 | 57 | let query_insert = "INSERT INTO cdrs_test.single_node_speculative_execution \ 58 | (id) VALUES (?)"; 59 | 60 | let items = vec!["1".to_string(), "2".to_string(), "3".to_string()]; 61 | 62 | for item in items { 63 | let values = query_values!(item); 64 | session 65 | .query_with_values(query_insert, values) 66 | .await 67 | .expect("insert item error"); 68 | } 69 | 70 | let cql = "SELECT * FROM cdrs_test.single_node_speculative_execution WHERE id IN ?"; 71 | let criteria = vec!["1".to_string(), "3".to_string()]; 72 | 73 | let rows = session 74 | .query_with_values(cql, query_values!(criteria.clone())) 75 | .await 76 | .expect("select values query error") 77 | .response_body() 78 | .expect("get body error") 79 | .into_rows() 80 | .expect("converting into rows error"); 81 | 82 | assert_eq!(rows.len(), criteria.len()); 83 | 84 | let found_all_matching_criteria = criteria.iter().all(|criteria_item: &String| { 85 | rows.iter().any(|row| { 86 | let id: String = row.get_r_by_name("id").expect("id"); 87 | 88 | criteria_item.clone() == id 89 | }) 90 | }); 91 | 92 | assert!( 93 | found_all_matching_criteria, 94 | "should find at least one element for each criteria" 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/multi_node_speculative_execution.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs_tokio::cluster::session::{SessionBuilder, TcpSessionBuilder}; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 9 | #[cfg(feature = "e2e-tests")] 10 | use common::*; 11 | #[cfg(feature = "e2e-tests")] 12 | use std::sync::Arc; 13 | #[cfg(feature = "e2e-tests")] 14 | use std::time::Duration; 15 | 16 | #[cfg(feature = "e2e-tests")] 17 | use cdrs_tokio::query_values; 18 | #[cfg(feature = "e2e-tests")] 19 | use cdrs_tokio::retry::NeverReconnectionPolicy; 20 | #[cfg(feature = "e2e-tests")] 21 | use cdrs_tokio::speculative_execution::ConstantSpeculativeExecutionPolicy; 22 | #[cfg(feature = "e2e-tests")] 23 | use cdrs_tokio::types::IntoRustByName; 24 | 25 | #[tokio::test] 26 | #[cfg(feature = "e2e-tests")] 27 | async fn multi_node_speculative_execution() { 28 | let cluster_config = NodeTcpConfigBuilder::new() 29 | .with_contact_point(ADDR.into()) 30 | .with_contact_point(ADDR.into()) 31 | .with_contact_point(ADDR.into()) 32 | .build() 33 | .await 34 | .unwrap(); 35 | 36 | let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 37 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 38 | .with_speculative_execution_policy(Box::new(ConstantSpeculativeExecutionPolicy::new( 39 | 5, 40 | Duration::from_secs(0), 41 | ))) 42 | .build() 43 | .await 44 | .unwrap(); 45 | 46 | let create_keyspace_query = "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 47 | replication = {'class': 'SimpleStrategy', 'replication_factor': 1} \ 48 | AND durable_writes = false"; 49 | session 50 | .query(create_keyspace_query) 51 | .await 52 | .expect("create keyspace error"); 53 | 54 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.single_node_speculative_execution \ 55 | (id text PRIMARY KEY)"; 56 | 57 | session.query(cql).await.expect("create table error"); 58 | 59 | let query_insert = "INSERT INTO cdrs_test.single_node_speculative_execution \ 60 | (id) VALUES (?)"; 61 | 62 | let items = vec!["1".to_string(), "2".to_string(), "3".to_string()]; 63 | 64 | for item in items { 65 | let values = query_values!(item); 66 | session 67 | .query_with_values(query_insert, values) 68 | .await 69 | .expect("insert item error"); 70 | } 71 | 72 | let cql = "SELECT * FROM cdrs_test.single_node_speculative_execution WHERE id IN ?"; 73 | let criteria = vec!["1".to_string(), "3".to_string()]; 74 | 75 | let rows = session 76 | .query_with_values(cql, query_values!(criteria.clone())) 77 | .await 78 | .expect("select values query error") 79 | .response_body() 80 | .expect("get body error") 81 | .into_rows() 82 | .expect("converting into rows error"); 83 | 84 | assert_eq!(rows.len(), criteria.len()); 85 | 86 | let found_all_matching_criteria = criteria.iter().all(|criteria_item: &String| { 87 | rows.iter().any(|row| { 88 | let id: String = row.get_r_by_name("id").expect("id"); 89 | 90 | criteria_item.clone() == id 91 | }) 92 | }); 93 | 94 | assert!( 95 | found_all_matching_criteria, 96 | "should find at least one element for each criteria" 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /cdrs-tokio/src/load_balancing/node_distance_evaluator.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use mockall::*; 3 | 4 | use crate::cluster::topology::NodeDistance; 5 | use crate::cluster::NodeInfo; 6 | 7 | /// A node distance evaluator evaluates given node distance in relation to the driver. 8 | #[cfg_attr(test, automock)] 9 | pub trait NodeDistanceEvaluator { 10 | /// Tries to compute a distance to a given node. Can return `None` if the distance cannot be 11 | /// determined. In such case, the nodes without a distance are expected to be ignored by load 12 | /// balancers. 13 | fn compute_distance(&self, node: &NodeInfo) -> Option; 14 | } 15 | 16 | /// A simple evaluator which treats all nodes as local. 17 | #[derive(Default, Debug)] 18 | pub struct AllLocalNodeDistanceEvaluator; 19 | 20 | impl NodeDistanceEvaluator for AllLocalNodeDistanceEvaluator { 21 | fn compute_distance(&self, _node: &NodeInfo) -> Option { 22 | Some(NodeDistance::Local) 23 | } 24 | } 25 | 26 | /// An evaluator which is aware of node location in relation to local DC. Built-in 27 | /// [`TopologyAwareLoadBalancingStrategy`](crate::load_balancing::TopologyAwareLoadBalancingStrategy) 28 | /// can use this information to properly identify which nodes to use in query plans. 29 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] 30 | pub struct TopologyAwareNodeDistanceEvaluator { 31 | local_dc: String, 32 | } 33 | 34 | impl NodeDistanceEvaluator for TopologyAwareNodeDistanceEvaluator { 35 | fn compute_distance(&self, node: &NodeInfo) -> Option { 36 | Some(if node.datacenter == self.local_dc { 37 | NodeDistance::Local 38 | } else { 39 | NodeDistance::Remote 40 | }) 41 | } 42 | } 43 | 44 | impl TopologyAwareNodeDistanceEvaluator { 45 | /// Local DC name represents the datacenter local to where the driver is running. 46 | pub fn new(local_dc: String) -> Self { 47 | TopologyAwareNodeDistanceEvaluator { local_dc } 48 | } 49 | } 50 | 51 | //noinspection DuplicatedCode 52 | #[cfg(test)] 53 | mod tests { 54 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 55 | use uuid::Uuid; 56 | 57 | use crate::cluster::topology::NodeDistance; 58 | use crate::cluster::NodeInfo; 59 | use crate::load_balancing::node_distance_evaluator::NodeDistanceEvaluator; 60 | use crate::load_balancing::node_distance_evaluator::TopologyAwareNodeDistanceEvaluator; 61 | 62 | #[test] 63 | fn should_return_topology_aware_distance() { 64 | let local_dc = "test"; 65 | let evaluator = TopologyAwareNodeDistanceEvaluator::new(local_dc.into()); 66 | 67 | assert_eq!( 68 | evaluator 69 | .compute_distance(&NodeInfo::new( 70 | Uuid::new_v4(), 71 | SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), 72 | None, 73 | "".into(), 74 | Default::default(), 75 | "".into(), 76 | )) 77 | .unwrap(), 78 | NodeDistance::Remote 79 | ); 80 | assert_eq!( 81 | evaluator 82 | .compute_distance(&NodeInfo::new( 83 | Uuid::new_v4(), 84 | SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), 85 | None, 86 | local_dc.into(), 87 | Default::default(), 88 | "".into(), 89 | )) 90 | .unwrap(), 91 | NodeDistance::Local 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /cdrs-tokio/tests/topology_aware.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs_tokio::cluster::session::SessionBuilder; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs_tokio::cluster::session::TcpSessionBuilder; 9 | #[cfg(feature = "e2e-tests")] 10 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 11 | #[cfg(feature = "e2e-tests")] 12 | use cdrs_tokio::load_balancing::node_distance_evaluator::TopologyAwareNodeDistanceEvaluator; 13 | #[cfg(feature = "e2e-tests")] 14 | use cdrs_tokio::load_balancing::TopologyAwareLoadBalancingStrategy; 15 | #[cfg(feature = "e2e-tests")] 16 | use std::sync::Arc; 17 | 18 | #[cfg(feature = "e2e-tests")] 19 | use cdrs_tokio::query_values; 20 | #[cfg(feature = "e2e-tests")] 21 | use cdrs_tokio::retry::NeverReconnectionPolicy; 22 | #[cfg(feature = "e2e-tests")] 23 | use cdrs_tokio::types::IntoRustByName; 24 | 25 | #[tokio::test] 26 | #[cfg(feature = "e2e-tests")] 27 | async fn query_topology_aware() { 28 | // this test is essentially the same as query values, but checks if topology aware load 29 | // balancing works 30 | 31 | let cluster_config = NodeTcpConfigBuilder::new() 32 | .with_contact_point("127.0.0.1:9042".into()) 33 | .with_authenticator_provider(Arc::new(NoneAuthenticatorProvider)) 34 | .build() 35 | .await 36 | .unwrap(); 37 | let session = TcpSessionBuilder::new( 38 | TopologyAwareLoadBalancingStrategy::new(None, false), 39 | cluster_config, 40 | ) 41 | .with_reconnection_policy(Arc::new(NeverReconnectionPolicy)) 42 | .with_node_distance_evaluator(Box::new(TopologyAwareNodeDistanceEvaluator::new( 43 | "datacenter1".into(), 44 | ))) 45 | .build() 46 | .await 47 | .unwrap(); 48 | 49 | let create_keyspace_query = "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 50 | replication = {'class': 'NetworkTopologyStrategy', 'datacenter1': 1} \ 51 | AND durable_writes = false"; 52 | session.query(create_keyspace_query).await.unwrap(); 53 | 54 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.test_query_values_in \ 55 | (id text PRIMARY KEY)"; 56 | 57 | session.query(cql).await.unwrap(); 58 | 59 | let query_insert = "INSERT INTO cdrs_test.test_query_values_in \ 60 | (id) VALUES (?)"; 61 | 62 | let items = vec!["1".to_string(), "2".to_string(), "3".to_string()]; 63 | 64 | for item in items { 65 | let values = query_values!(item); 66 | session 67 | .query_with_values(query_insert, values) 68 | .await 69 | .expect("insert item error"); 70 | } 71 | 72 | let cql = "SELECT * FROM cdrs_test.test_query_values_in WHERE id IN ?"; 73 | let criteria = vec!["1".to_string(), "3".to_string()]; 74 | 75 | let rows = session 76 | .query_with_values(cql, query_values!(criteria.clone())) 77 | .await 78 | .expect("select values query error") 79 | .response_body() 80 | .expect("get body error") 81 | .into_rows() 82 | .expect("converting into rows error"); 83 | 84 | assert_eq!(rows.len(), criteria.len()); 85 | 86 | let found_all_matching_criteria = criteria.iter().all(|criteria_item: &String| { 87 | rows.iter().any(|row| { 88 | let id: String = row.get_r_by_name("id").expect("id"); 89 | 90 | criteria_item.clone() == id 91 | }) 92 | }); 93 | 94 | assert!( 95 | found_all_matching_criteria, 96 | "should find at least one element for each criteria" 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /cdrs-tokio/examples/prepare_batch_execute.rs: -------------------------------------------------------------------------------- 1 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 2 | use cdrs_tokio::cluster::session::{Session, SessionBuilder, TcpSessionBuilder}; 3 | use cdrs_tokio::cluster::{NodeTcpConfigBuilder, TcpConnectionManager}; 4 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 5 | use cdrs_tokio::query::*; 6 | use cdrs_tokio::query_values; 7 | use cdrs_tokio::transport::TransportTcp; 8 | use cdrs_tokio::{IntoCdrsValue, TryFromRow}; 9 | use std::sync::Arc; 10 | 11 | type CurrentSession = Session< 12 | TransportTcp, 13 | TcpConnectionManager, 14 | RoundRobinLoadBalancingStrategy, 15 | >; 16 | 17 | #[derive(Clone, Debug, IntoCdrsValue, TryFromRow, PartialEq)] 18 | struct RowStruct { 19 | key: i32, 20 | } 21 | 22 | impl RowStruct { 23 | fn into_query_values(self) -> QueryValues { 24 | // **IMPORTANT NOTE:** query values should be WITHOUT NAMES 25 | // https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec#L413 26 | query_values!(self.key) 27 | } 28 | } 29 | 30 | #[tokio::main] 31 | async fn main() { 32 | let cluster_config = NodeTcpConfigBuilder::new() 33 | .with_contact_point("127.0.0.1:9042".into()) 34 | .with_authenticator_provider(Arc::new(NoneAuthenticatorProvider)) 35 | .build() 36 | .await 37 | .unwrap(); 38 | let lb = RoundRobinLoadBalancingStrategy::new(); 39 | let mut session = TcpSessionBuilder::new(lb, cluster_config) 40 | .build() 41 | .await 42 | .unwrap(); 43 | 44 | create_keyspace(&mut session).await; 45 | create_table(&mut session).await; 46 | 47 | let insert_struct_cql = "INSERT INTO test_ks.my_test_table (key) VALUES (?)"; 48 | let prepared_query = session 49 | .prepare(insert_struct_cql) 50 | .await 51 | .expect("Prepare query error"); 52 | 53 | for k in 100..110 { 54 | let row = RowStruct { key: k }; 55 | 56 | insert_row(&mut session, row, &prepared_query).await; 57 | } 58 | 59 | batch_few_queries(&mut session, insert_struct_cql).await; 60 | } 61 | 62 | async fn create_keyspace(session: &mut CurrentSession) { 63 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 64 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 65 | session 66 | .query(create_ks) 67 | .await 68 | .expect("Keyspace creation error"); 69 | } 70 | 71 | async fn create_table(session: &mut CurrentSession) { 72 | let create_table_cql = 73 | "CREATE TABLE IF NOT EXISTS test_ks.my_test_table (key int PRIMARY KEY);"; 74 | session 75 | .query(create_table_cql) 76 | .await 77 | .expect("Table creation error"); 78 | } 79 | 80 | async fn insert_row(session: &mut CurrentSession, row: RowStruct, prepared_query: &PreparedQuery) { 81 | session 82 | .exec_with_values(prepared_query, row.into_query_values()) 83 | .await 84 | .expect("exec_with_values error"); 85 | } 86 | 87 | async fn batch_few_queries(session: &mut CurrentSession, query: &str) { 88 | let prepared_query = session.prepare(query).await.expect("Prepare query error"); 89 | let row_1 = RowStruct { key: 1001 }; 90 | let row_2 = RowStruct { key: 2001 }; 91 | 92 | let batch = BatchQueryBuilder::new() 93 | .add_query_prepared(&prepared_query, row_1.into_query_values()) 94 | .add_query(query, row_2.into_query_values()) 95 | .build() 96 | .expect("batch builder"); 97 | 98 | session.batch(batch).await.expect("batch query error"); 99 | } 100 | -------------------------------------------------------------------------------- /cdrs-tokio/examples/multiple_thread.rs: -------------------------------------------------------------------------------- 1 | use cdrs_tokio::authenticators::NoneAuthenticatorProvider; 2 | use cdrs_tokio::cluster::session::{Session, SessionBuilder, TcpSessionBuilder}; 3 | use cdrs_tokio::cluster::{NodeTcpConfigBuilder, TcpConnectionManager}; 4 | use cdrs_tokio::frame::TryFromRow; 5 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 6 | use cdrs_tokio::query::*; 7 | use cdrs_tokio::query_values; 8 | use cdrs_tokio::transport::TransportTcp; 9 | use cdrs_tokio::{IntoCdrsValue, TryFromRow, TryFromUdt}; 10 | use std::sync::Arc; 11 | 12 | type CurrentSession = Session< 13 | TransportTcp, 14 | TcpConnectionManager, 15 | RoundRobinLoadBalancingStrategy, 16 | >; 17 | 18 | #[tokio::main] 19 | async fn main() { 20 | let cluster_config = NodeTcpConfigBuilder::new() 21 | .with_contact_point("127.0.0.1:9042".into()) 22 | .with_authenticator_provider(Arc::new(NoneAuthenticatorProvider)) 23 | .build() 24 | .await 25 | .unwrap(); 26 | let lb = RoundRobinLoadBalancingStrategy::new(); 27 | let session: Arc = Arc::new( 28 | TcpSessionBuilder::new(lb, cluster_config) 29 | .build() 30 | .await 31 | .unwrap(), 32 | ); 33 | 34 | create_keyspace(session.clone()).await; 35 | create_table(session.clone()).await; 36 | 37 | let futures: Vec> = (0..20) 38 | .map(|i| { 39 | let thread_session = session.clone(); 40 | tokio::spawn(insert_struct(thread_session, i)) 41 | }) 42 | .collect(); 43 | 44 | let _responses = futures::future::join_all(futures); 45 | 46 | select_struct(session).await; 47 | } 48 | 49 | #[derive(Clone, Debug, IntoCdrsValue, TryFromRow, PartialEq)] 50 | struct RowStruct { 51 | key: i32, 52 | } 53 | 54 | impl RowStruct { 55 | fn into_query_values(self) -> QueryValues { 56 | query_values!("key" => self.key) 57 | } 58 | } 59 | 60 | #[derive(Debug, Clone, PartialEq, IntoCdrsValue, TryFromUdt)] 61 | struct User { 62 | username: String, 63 | } 64 | 65 | async fn create_keyspace(session: Arc) { 66 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 67 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 68 | session 69 | .query(create_ks) 70 | .await 71 | .expect("Keyspace creation error"); 72 | } 73 | 74 | async fn create_table(session: Arc) { 75 | let create_table_cql = 76 | "CREATE TABLE IF NOT EXISTS test_ks.multi_thread_table (key int PRIMARY KEY);"; 77 | session 78 | .query(create_table_cql) 79 | .await 80 | .expect("Table creation error"); 81 | } 82 | 83 | async fn insert_struct(session: Arc, key: i32) { 84 | let row = RowStruct { key }; 85 | 86 | let insert_struct_cql = "INSERT INTO test_ks.multi_thread_table (key) VALUES (?)"; 87 | session 88 | .query_with_values(insert_struct_cql, row.into_query_values()) 89 | .await 90 | .expect("insert"); 91 | } 92 | 93 | async fn select_struct(session: Arc) { 94 | let select_struct_cql = "SELECT * FROM test_ks.multi_thread_table"; 95 | let rows = session 96 | .query(select_struct_cql) 97 | .await 98 | .expect("query") 99 | .response_body() 100 | .expect("get body") 101 | .into_rows() 102 | .expect("into rows"); 103 | 104 | for row in rows { 105 | let my_row: RowStruct = RowStruct::try_from_row(row).expect("into RowStruct"); 106 | println!("struct got: {my_row:?}"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cdrs-tokio/src/envelope_parser.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::io::Cursor; 3 | use std::net::SocketAddr; 4 | use tokio::io::AsyncReadExt; 5 | 6 | use cassandra_protocol::compression::Compression; 7 | use cassandra_protocol::error; 8 | use cassandra_protocol::frame::message_response::ResponseBody; 9 | use cassandra_protocol::frame::{ 10 | Direction, Envelope, Flags, Opcode, Version, LENGTH_LEN, STREAM_LEN, 11 | }; 12 | use cassandra_protocol::types::data_serialization_types::decode_timeuuid; 13 | use cassandra_protocol::types::{ 14 | from_cursor_string_list, try_i16_from_bytes, try_i32_from_bytes, UUID_LEN, 15 | }; 16 | 17 | async fn parse_raw_envelope( 18 | cursor: &mut T, 19 | compressor: Compression, 20 | ) -> error::Result { 21 | let mut version_bytes = [0; Version::BYTE_LENGTH]; 22 | let mut flag_bytes = [0; Flags::BYTE_LENGTH]; 23 | let mut opcode_bytes = [0; Opcode::BYTE_LENGTH]; 24 | let mut stream_bytes = [0; STREAM_LEN]; 25 | let mut length_bytes = [0; LENGTH_LEN]; 26 | 27 | // NOTE: order of reads matters 28 | cursor.read_exact(&mut version_bytes).await?; 29 | cursor.read_exact(&mut flag_bytes).await?; 30 | cursor.read_exact(&mut stream_bytes).await?; 31 | cursor.read_exact(&mut opcode_bytes).await?; 32 | cursor.read_exact(&mut length_bytes).await?; 33 | 34 | let version = Version::try_from(version_bytes[0])?; 35 | let direction = Direction::from(version_bytes[0]); 36 | let flags = Flags::from_bits_truncate(flag_bytes[0]); 37 | let stream_id = try_i16_from_bytes(&stream_bytes)?; 38 | let opcode = Opcode::try_from(opcode_bytes[0])?; 39 | let length = try_i32_from_bytes(&length_bytes)? as usize; 40 | 41 | let mut body_bytes = vec![0; length]; 42 | 43 | cursor.read_exact(&mut body_bytes).await?; 44 | 45 | let full_body = if flags.contains(Flags::COMPRESSION) { 46 | compressor.decode(body_bytes)? 47 | } else { 48 | Compression::None.decode(body_bytes)? 49 | }; 50 | 51 | let body_len = full_body.len(); 52 | 53 | // Use cursor to get tracing id, warnings and actual body 54 | let mut body_cursor = Cursor::new(full_body.as_slice()); 55 | 56 | let tracing_id = if flags.contains(Flags::TRACING) { 57 | let mut tracing_bytes = [0; UUID_LEN]; 58 | std::io::Read::read_exact(&mut body_cursor, &mut tracing_bytes)?; 59 | 60 | decode_timeuuid(&tracing_bytes).ok() 61 | } else { 62 | None 63 | }; 64 | 65 | let warnings = if flags.contains(Flags::WARNING) { 66 | from_cursor_string_list(&mut body_cursor)? 67 | } else { 68 | vec![] 69 | }; 70 | 71 | let mut body = Vec::with_capacity(body_len - body_cursor.position() as usize); 72 | 73 | std::io::Read::read_to_end(&mut body_cursor, &mut body)?; 74 | 75 | let envelope = Envelope { 76 | version, 77 | direction, 78 | flags, 79 | opcode, 80 | stream_id, 81 | body, 82 | tracing_id, 83 | warnings, 84 | }; 85 | 86 | Ok(envelope) 87 | } 88 | 89 | pub async fn parse_envelope( 90 | cursor: &mut T, 91 | compressor: Compression, 92 | addr: SocketAddr, 93 | ) -> error::Result { 94 | let envelope = parse_raw_envelope(cursor, compressor).await?; 95 | convert_envelope_into_result(envelope, addr) 96 | } 97 | 98 | pub(crate) fn convert_envelope_into_result( 99 | envelope: Envelope, 100 | addr: SocketAddr, 101 | ) -> error::Result { 102 | match envelope.opcode { 103 | Opcode::Error => envelope.response_body().and_then(|err| match err { 104 | ResponseBody::Error(err) => Err(error::Error::Server { body: err, addr }), 105 | _ => unreachable!(), 106 | }), 107 | _ => Ok(envelope), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/query_params_builder.rs: -------------------------------------------------------------------------------- 1 | use super::{QueryFlags, QueryParams, QueryValues}; 2 | use crate::consistency::Consistency; 3 | use crate::types::{CBytes, CInt, CLong}; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct QueryParamsBuilder { 7 | consistency: Consistency, 8 | flags: Option, 9 | values: Option, 10 | with_names: bool, 11 | page_size: Option, 12 | paging_state: Option, 13 | serial_consistency: Option, 14 | timestamp: Option, 15 | keyspace: Option, 16 | now_in_seconds: Option, 17 | } 18 | 19 | impl QueryParamsBuilder { 20 | /// Factory function that returns new `QueryBuilder`. 21 | pub fn new() -> QueryParamsBuilder { 22 | Default::default() 23 | } 24 | 25 | /// Sets new query consistency 26 | #[must_use] 27 | pub fn with_consistency(mut self, consistency: Consistency) -> Self { 28 | self.consistency = consistency; 29 | self 30 | } 31 | 32 | /// Sets new flags. 33 | #[must_use] 34 | pub fn with_flags(mut self, flags: QueryFlags) -> Self { 35 | self.flags = Some(flags); 36 | self 37 | } 38 | 39 | /// Sets new query values. 40 | #[must_use] 41 | pub fn with_values(mut self, values: QueryValues) -> Self { 42 | self.with_names = values.has_names(); 43 | self.values = Some(values); 44 | self.flags = self.flags.or_else(|| { 45 | let mut flags = QueryFlags::VALUE; 46 | if self.with_names { 47 | flags.insert(QueryFlags::WITH_NAMES_FOR_VALUES); 48 | } 49 | Some(flags) 50 | }); 51 | 52 | self 53 | } 54 | 55 | /// Sets the "with names for values" flag 56 | #[must_use] 57 | pub fn with_names(mut self, with_names: bool) -> Self { 58 | self.with_names = with_names; 59 | self 60 | } 61 | 62 | /// Sets new query consistency. 63 | #[must_use] 64 | pub fn with_page_size(mut self, size: CInt) -> Self { 65 | self.page_size = Some(size); 66 | self.flags = self.flags.or(Some(QueryFlags::PAGE_SIZE)); 67 | 68 | self 69 | } 70 | 71 | /// Sets new query consistency. 72 | #[must_use] 73 | pub fn with_paging_state(mut self, state: CBytes) -> Self { 74 | self.paging_state = Some(state); 75 | self.flags = self.flags.or(Some(QueryFlags::WITH_PAGING_STATE)); 76 | 77 | self 78 | } 79 | 80 | /// Sets new serial consistency. 81 | #[must_use] 82 | pub fn with_serial_consistency(mut self, serial_consistency: Consistency) -> Self { 83 | self.serial_consistency = Some(serial_consistency); 84 | self 85 | } 86 | 87 | /// Sets new timestamp. 88 | #[must_use] 89 | pub fn with_timestamp(mut self, timestamp: CLong) -> Self { 90 | self.timestamp = Some(timestamp); 91 | self 92 | } 93 | 94 | /// Overrides used keyspace. 95 | #[must_use] 96 | pub fn with_keyspace(mut self, keyspace: String) -> Self { 97 | self.keyspace = Some(keyspace); 98 | self 99 | } 100 | 101 | /// Sets "now" in seconds. 102 | #[must_use] 103 | pub fn with_now_in_seconds(mut self, now_in_seconds: CInt) -> Self { 104 | self.now_in_seconds = Some(now_in_seconds); 105 | self 106 | } 107 | 108 | /// Finalizes query building process and returns query itself 109 | #[must_use] 110 | pub fn build(self) -> QueryParams { 111 | QueryParams { 112 | consistency: self.consistency, 113 | values: self.values, 114 | with_names: self.with_names, 115 | page_size: self.page_size, 116 | paging_state: self.paging_state, 117 | serial_consistency: self.serial_consistency, 118 | timestamp: self.timestamp, 119 | keyspace: self.keyspace, 120 | now_in_seconds: self.now_in_seconds, 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /cassandra-protocol/src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use bytes::Buf; 3 | use derive_more::Constructor; 4 | use std::cmp::min; 5 | use std::convert::TryFrom; 6 | use std::num::Wrapping; 7 | 8 | const C1: Wrapping = Wrapping(0x87c3_7b91_1142_53d5_u64 as i64); 9 | const C2: Wrapping = Wrapping(0x4cf5_ad43_2745_937f_u64 as i64); 10 | 11 | /// A token on the ring. Only Murmur3 tokens are supported for now. 12 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug, Hash, Constructor)] 13 | pub struct Murmur3Token { 14 | pub value: i64, 15 | } 16 | 17 | impl Murmur3Token { 18 | // based on buggy Cassandra implementation 19 | pub fn generate(mut routing_key: &[u8]) -> Self { 20 | let length = routing_key.len(); 21 | 22 | let mut h1: Wrapping = Wrapping(0); 23 | let mut h2: Wrapping = Wrapping(0); 24 | 25 | while routing_key.len() >= 16 { 26 | let mut k1 = Wrapping(routing_key.get_i64_le()); 27 | let mut k2 = Wrapping(routing_key.get_i64_le()); 28 | 29 | k1 *= C1; 30 | k1 = rotl64(k1, 31); 31 | k1 *= C2; 32 | h1 ^= k1; 33 | 34 | h1 = rotl64(h1, 27); 35 | h1 += h2; 36 | h1 = h1 * Wrapping(5) + Wrapping(0x52dce729); 37 | 38 | k2 *= C2; 39 | k2 = rotl64(k2, 33); 40 | k2 *= C1; 41 | h2 ^= k2; 42 | 43 | h2 = rotl64(h2, 31); 44 | h2 += h1; 45 | h2 = h2 * Wrapping(5) + Wrapping(0x38495ab5); 46 | } 47 | 48 | let mut k1 = Wrapping(0_i64); 49 | let mut k2 = Wrapping(0_i64); 50 | 51 | debug_assert!(routing_key.len() < 16); 52 | 53 | if routing_key.len() > 8 { 54 | for i in (8..routing_key.len()).rev() { 55 | k2 ^= Wrapping(routing_key[i] as i8 as i64) << ((i - 8) * 8); 56 | } 57 | 58 | k2 *= C2; 59 | k2 = rotl64(k2, 33); 60 | k2 *= C1; 61 | h2 ^= k2; 62 | } 63 | 64 | if !routing_key.is_empty() { 65 | for i in (0..min(8, routing_key.len())).rev() { 66 | k1 ^= Wrapping(routing_key[i] as i8 as i64) << (i * 8); 67 | } 68 | 69 | k1 *= C1; 70 | k1 = rotl64(k1, 31); 71 | k1 *= C2; 72 | h1 ^= k1; 73 | } 74 | 75 | h1 ^= Wrapping(length as i64); 76 | h2 ^= Wrapping(length as i64); 77 | 78 | h1 += h2; 79 | h2 += h1; 80 | 81 | h1 = fmix(h1); 82 | h2 = fmix(h2); 83 | 84 | h1 += h2; 85 | 86 | Murmur3Token::new(h1.0) 87 | } 88 | } 89 | 90 | impl TryFrom for Murmur3Token { 91 | type Error = Error; 92 | 93 | fn try_from(value: String) -> Result { 94 | value 95 | .parse() 96 | .map_err(|error| format!("Error parsing token: {error}").into()) 97 | .map(Murmur3Token::new) 98 | } 99 | } 100 | 101 | impl From for Murmur3Token { 102 | fn from(value: i64) -> Self { 103 | Murmur3Token::new(value) 104 | } 105 | } 106 | 107 | #[inline] 108 | fn rotl64(v: Wrapping, n: u32) -> Wrapping { 109 | Wrapping((v.0 << n) | (v.0 as u64 >> (64 - n)) as i64) 110 | } 111 | 112 | #[inline] 113 | fn fmix(mut k: Wrapping) -> Wrapping { 114 | k ^= Wrapping((k.0 as u64 >> 33) as i64); 115 | k *= Wrapping(0xff51afd7ed558ccd_u64 as i64); 116 | k ^= Wrapping((k.0 as u64 >> 33) as i64); 117 | k *= Wrapping(0xc4ceb9fe1a85ec53_u64 as i64); 118 | k ^= Wrapping((k.0 as u64 >> 33) as i64); 119 | 120 | k 121 | } 122 | 123 | #[cfg(test)] 124 | mod test { 125 | use super::*; 126 | 127 | #[test] 128 | fn test_generate_murmur3_token() { 129 | for s in [ 130 | ("testvalue", 5965290492934326460), 131 | ("testvalue123", 1518494936189046133), 132 | ("example_key", -7813763279771224608), 133 | ("château", 9114062196463836094), 134 | ] { 135 | let generated_token = Murmur3Token::generate(s.0.as_bytes()); 136 | assert_eq!(generated_token.value, s.1); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/config_tcp.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::authenticators::{NoneAuthenticatorProvider, SaslAuthenticatorProvider}; 2 | use cassandra_protocol::error::Result; 3 | use cassandra_protocol::frame::Version; 4 | use derivative::Derivative; 5 | use std::net::SocketAddr; 6 | use std::sync::Arc; 7 | 8 | #[cfg(feature = "http-proxy")] 9 | use crate::cluster::HttpProxyConfig; 10 | use crate::cluster::NodeAddress; 11 | 12 | /// Single node TCP connection config. See [NodeTcpConfigBuilder]. 13 | #[derive(Derivative, Clone)] 14 | #[derivative(Debug)] 15 | pub struct NodeTcpConfig { 16 | pub(crate) contact_points: Vec, 17 | #[derivative(Debug = "ignore")] 18 | pub(crate) authenticator_provider: Arc, 19 | pub(crate) version: Version, 20 | pub(crate) beta_protocol: bool, 21 | #[cfg(feature = "http-proxy")] 22 | pub(crate) http_proxy: Option, 23 | } 24 | 25 | /// Builder structure that helps to configure TCP connection for node. 26 | #[derive(Derivative, Clone)] 27 | #[derivative(Debug)] 28 | pub struct NodeTcpConfigBuilder { 29 | addrs: Vec, 30 | #[derivative(Debug = "ignore")] 31 | authenticator_provider: Arc, 32 | version: Version, 33 | beta_protocol: bool, 34 | #[cfg(feature = "http-proxy")] 35 | http_proxy: Option, 36 | } 37 | 38 | impl Default for NodeTcpConfigBuilder { 39 | fn default() -> Self { 40 | NodeTcpConfigBuilder { 41 | addrs: vec![], 42 | authenticator_provider: Arc::new(NoneAuthenticatorProvider), 43 | version: Version::V4, 44 | beta_protocol: false, 45 | #[cfg(feature = "http-proxy")] 46 | http_proxy: None, 47 | } 48 | } 49 | } 50 | 51 | impl NodeTcpConfigBuilder { 52 | pub fn new() -> NodeTcpConfigBuilder { 53 | Default::default() 54 | } 55 | 56 | /// Sets new authenticator. 57 | #[must_use] 58 | pub fn with_authenticator_provider( 59 | mut self, 60 | authenticator_provider: Arc, 61 | ) -> Self { 62 | self.authenticator_provider = authenticator_provider; 63 | self 64 | } 65 | 66 | /// Adds initial node address (a contact point). Contact points are considered local to the 67 | /// driver until a topology refresh occurs. 68 | #[must_use] 69 | pub fn with_contact_point(mut self, addr: NodeAddress) -> Self { 70 | self.addrs.push(addr); 71 | self 72 | } 73 | 74 | /// Adds initial node addresses 75 | #[must_use] 76 | pub fn with_contact_points(mut self, addr: Vec) -> Self { 77 | self.addrs.extend(addr); 78 | self 79 | } 80 | 81 | /// Set cassandra protocol version 82 | #[must_use] 83 | pub fn with_version(mut self, version: Version) -> Self { 84 | self.version = version; 85 | self 86 | } 87 | 88 | /// Sets beta protocol usage flag 89 | #[must_use] 90 | pub fn with_beta_protocol(mut self, beta_protocol: bool) -> Self { 91 | self.beta_protocol = beta_protocol; 92 | self 93 | } 94 | 95 | /// Adds HTTP proxy configuration 96 | #[cfg(feature = "http-proxy")] 97 | #[must_use] 98 | pub fn with_http_proxy(mut self, config: HttpProxyConfig) -> Self { 99 | self.http_proxy = Some(config); 100 | self 101 | } 102 | 103 | /// Finalizes building process 104 | pub async fn build(self) -> Result { 105 | // replace with map() when async lambdas become available 106 | let mut contact_points = Vec::with_capacity(self.addrs.len()); 107 | for contact_point in self.addrs { 108 | contact_points.append(&mut contact_point.resolve_address().await?); 109 | } 110 | 111 | Ok(NodeTcpConfig { 112 | contact_points, 113 | authenticator_provider: self.authenticator_provider, 114 | version: self.version, 115 | beta_protocol: self.beta_protocol, 116 | #[cfg(feature = "http-proxy")] 117 | http_proxy: self.http_proxy, 118 | }) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/decimal.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Constructor; 2 | use float_eq::*; 3 | use num_bigint::BigInt; 4 | use std::io::Cursor; 5 | 6 | use crate::frame::{Serialize, Version}; 7 | 8 | /// Cassandra Decimal type 9 | #[derive(Debug, Clone, PartialEq, Constructor, Ord, PartialOrd, Eq, Hash)] 10 | pub struct Decimal { 11 | pub unscaled: BigInt, 12 | pub scale: i32, 13 | } 14 | 15 | impl Decimal { 16 | /// Method that returns plain `BigInt` value. 17 | pub fn as_plain(&self) -> BigInt { 18 | self.unscaled.clone() / 10i64.pow(self.scale as u32) 19 | } 20 | } 21 | 22 | impl Serialize for Decimal { 23 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 24 | self.scale.serialize(cursor, version); 25 | self.unscaled 26 | .to_signed_bytes_be() 27 | .serialize(cursor, version); 28 | } 29 | } 30 | 31 | macro_rules! impl_from_for_decimal { 32 | ($t:ty) => { 33 | impl From<$t> for Decimal { 34 | fn from(i: $t) -> Self { 35 | Decimal { 36 | unscaled: i.into(), 37 | scale: 0, 38 | } 39 | } 40 | } 41 | }; 42 | } 43 | 44 | impl_from_for_decimal!(i8); 45 | impl_from_for_decimal!(i16); 46 | impl_from_for_decimal!(i32); 47 | impl_from_for_decimal!(i64); 48 | impl_from_for_decimal!(u8); 49 | impl_from_for_decimal!(u16); 50 | 51 | impl From for Decimal { 52 | fn from(f: f32) -> Decimal { 53 | let mut scale = 0; 54 | 55 | loop { 56 | let unscaled = f * (10i64.pow(scale) as f32); 57 | 58 | if float_eq!(unscaled, unscaled.trunc(), abs <= f32::EPSILON) { 59 | return Decimal::new((unscaled as i64).into(), scale as i32); 60 | } 61 | 62 | scale += 1; 63 | } 64 | } 65 | } 66 | 67 | impl From for Decimal { 68 | fn from(f: f64) -> Decimal { 69 | let mut scale = 0; 70 | 71 | loop { 72 | let unscaled = f * (10i64.pow(scale) as f64); 73 | 74 | if float_eq!(unscaled, unscaled.trunc(), abs <= f64::EPSILON) { 75 | return Decimal::new((unscaled as i64).into(), scale as i32); 76 | } 77 | 78 | scale += 1; 79 | } 80 | } 81 | } 82 | 83 | impl From for BigInt { 84 | fn from(value: Decimal) -> Self { 85 | value.as_plain() 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod test { 91 | use super::*; 92 | 93 | #[test] 94 | fn serialize_test() { 95 | assert_eq!( 96 | Decimal::new(129.into(), 0).serialize_to_vec(Version::V4), 97 | vec![0, 0, 0, 0, 0x00, 0x81] 98 | ); 99 | 100 | assert_eq!( 101 | Decimal::new(BigInt::from(-129), 0).serialize_to_vec(Version::V4), 102 | vec![0, 0, 0, 0, 0xFF, 0x7F] 103 | ); 104 | 105 | let expected: Vec = vec![0, 0, 0, 1, 0x00, 0x81]; 106 | assert_eq!( 107 | Decimal::new(129.into(), 1).serialize_to_vec(Version::V4), 108 | expected 109 | ); 110 | 111 | let expected: Vec = vec![0, 0, 0, 1, 0xFF, 0x7F]; 112 | assert_eq!( 113 | Decimal::new(BigInt::from(-129), 1).serialize_to_vec(Version::V4), 114 | expected 115 | ); 116 | } 117 | 118 | #[test] 119 | fn from_f32() { 120 | assert_eq!( 121 | Decimal::from(12300001_f32), 122 | Decimal::new(12300001.into(), 0) 123 | ); 124 | assert_eq!( 125 | Decimal::from(1230000.1_f32), 126 | Decimal::new(12300001.into(), 1) 127 | ); 128 | assert_eq!( 129 | Decimal::from(0.12300001_f32), 130 | Decimal::new(12300001.into(), 8) 131 | ); 132 | } 133 | 134 | #[test] 135 | fn from_f64() { 136 | assert_eq!( 137 | Decimal::from(1230000000000001_f64), 138 | Decimal::new(1230000000000001i64.into(), 0) 139 | ); 140 | assert_eq!( 141 | Decimal::from(123000000000000.1f64), 142 | Decimal::new(1230000000000001i64.into(), 1) 143 | ); 144 | assert_eq!( 145 | Decimal::from(0.1230000000000001f64), 146 | Decimal::new(1230000000000001i64.into(), 16) 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDRS tokio 2 | 3 | [![crates.io version](https://img.shields.io/crates/v/cdrs-tokio.svg)](https://crates.io/crates/cdrs-tokio) ![build status](https://github.com/krojew/cdrs-tokio/actions/workflows/rust.yml/badge.svg) 4 | 5 | ![CDRS tokio - async Apache Cassandra driver using tokio](./cdrs-logo.png) 6 | 7 | CDRS is production-ready Apache **C**assandra **d**river written in pure **R**u* 8 | *s**t. Focuses on providing high 9 | level of configurability to suit most use cases at any scale, as its Java 10 | counterpart, while also leveraging the 11 | safety and performance of Rust. 12 | 13 | ## Features 14 | 15 | - Asynchronous API; 16 | - TCP/TLS connection (rustls); 17 | - Topology-aware dynamic and configurable load balancing; 18 | - Configurable connection strategies and pools; 19 | - Configurable speculative execution; 20 | - LZ4, Snappy compression; 21 | - Cassandra-to-Rust data serialization/deserialization with custom type support; 22 | - Pluggable authentication strategies; 23 | - [ScyllaDB](https://www.scylladb.com/) support; 24 | - Server events listening; 25 | - Multiple CQL version support (3, 4, 5), full spec implementation; 26 | - Query tracing information; 27 | - Prepared statements; 28 | - Query paging; 29 | - Batch statements; 30 | - Configurable retry and reconnection policy; 31 | - Support for interleaved queries; 32 | - Support for Yugabyte YCQL JSONB; 33 | - Support for beta protocol usage; 34 | 35 | ## Performance 36 | 37 | Due to high configurability of **CDRS**, the performance will vary depending on 38 | use case. The following benchmarks 39 | have been made against the latest (master as of 03-12-2021) versions of 40 | respective libraries (except 41 | cassandra-cpp: 2.16.0) and protocol version 4. 42 | 43 | - `cdrs-tokio-large-pool` - **CDRS** with node connection pool equal to double 44 | of physical CPU cores 45 | - `cdrs-tokio-small-pool` - **CDRS** with a single connection per node 46 | - `scylladb-rust-large-pool` - `scylla` crate with node connection pool equal to 47 | double of physical CPU cores 48 | - `scylladb-rust-small-pool` - `scylla` crate with a single connection per node 49 | - `cassandra-cpp` - Rust bindings for Datastax C++ Driver, running on multiple 50 | threads using Tokio 51 | - `gocql` - a driver written in Go 52 | 53 | insert benchmark 54 | select benchmark 55 | mixed benchmark 56 | 57 | Knowing given use case, CDRS can be optimized for peak performance. 58 | 59 | ## Documentation and examples 60 | 61 | - [User guide](./documentation). 62 | - [Examples](./cdrs-tokio/examples). 63 | - [API docs](https://docs.rs/cdrs-tokio/latest/cdrs_tokio/). 64 | - Using ScyllaDB with 65 | RUST [lesson](https://university.scylladb.com/courses/using-scylla-drivers/lessons/rust-and-scylla/). 66 | 67 | ## Getting started 68 | 69 | This example configures a cluster consisting of a single node without 70 | authentication, and uses round-robin 71 | load balancing. Other options are kept as default. 72 | 73 | ```rust 74 | use cdrs_tokio::cluster::session::{TcpSessionBuilder, SessionBuilder}; 75 | use cdrs_tokio::cluster::NodeTcpConfigBuilder; 76 | use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy; 77 | use cdrs_tokio::query::*; 78 | 79 | #[tokio::main] 80 | async fn main() { 81 | let cluster_config = NodeTcpConfigBuilder::new() 82 | .with_contact_point("127.0.0.1:9042".into()) 83 | .build() 84 | .await 85 | .unwrap(); 86 | let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config) 87 | .build() 88 | .await 89 | .unwrap(); 90 | 91 | let create_ks = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 92 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 93 | session 94 | .query(create_ks) 95 | .await 96 | .expect("Keyspace create error"); 97 | } 98 | ``` 99 | 100 | ## License 101 | 102 | This project is licensed under either of 103 | 104 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) 105 | or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 106 | - MIT license ([LICENSE-MIT](LICENSE-MIT) 107 | or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 108 | 109 | at your option. 110 | -------------------------------------------------------------------------------- /cdrs-tokio/src/cluster/config_rustls.rs: -------------------------------------------------------------------------------- 1 | use cassandra_protocol::authenticators::{NoneAuthenticatorProvider, SaslAuthenticatorProvider}; 2 | use cassandra_protocol::error::Result; 3 | use cassandra_protocol::frame::Version; 4 | use derivative::Derivative; 5 | use std::net::SocketAddr; 6 | use std::sync::Arc; 7 | use tokio_rustls::rustls::{pki_types::ServerName, ClientConfig}; 8 | 9 | #[cfg(feature = "http-proxy")] 10 | use crate::cluster::HttpProxyConfig; 11 | use crate::cluster::NodeAddress; 12 | 13 | /// Single node TLS connection config. See [NodeRustlsConfigBuilder]. 14 | #[derive(Derivative, Clone)] 15 | #[derivative(Debug)] 16 | pub struct NodeRustlsConfig { 17 | pub(crate) contact_points: Vec, 18 | pub(crate) dns_name: ServerName<'static>, 19 | #[derivative(Debug = "ignore")] 20 | pub(crate) authenticator_provider: Arc, 21 | pub(crate) config: Arc, 22 | pub(crate) version: Version, 23 | pub(crate) beta_protocol: bool, 24 | #[cfg(feature = "http-proxy")] 25 | pub(crate) http_proxy: Option, 26 | } 27 | 28 | /// Builder structure that helps to configure TLS connection for node. 29 | #[derive(Derivative, Clone)] 30 | #[derivative(Debug)] 31 | pub struct NodeRustlsConfigBuilder { 32 | addrs: Vec, 33 | dns_name: ServerName<'static>, 34 | #[derivative(Debug = "ignore")] 35 | authenticator_provider: Arc, 36 | config: Arc, 37 | version: Version, 38 | beta_protocol: bool, 39 | #[cfg(feature = "http-proxy")] 40 | http_proxy: Option, 41 | } 42 | 43 | impl NodeRustlsConfigBuilder { 44 | pub fn new(dns_name: ServerName<'static>, config: Arc) -> Self { 45 | NodeRustlsConfigBuilder { 46 | addrs: vec![], 47 | dns_name, 48 | authenticator_provider: Arc::new(NoneAuthenticatorProvider), 49 | config, 50 | version: Version::V4, 51 | beta_protocol: false, 52 | #[cfg(feature = "http-proxy")] 53 | http_proxy: None, 54 | } 55 | } 56 | 57 | /// Sets new authenticator. 58 | #[must_use] 59 | pub fn with_authenticator_provider( 60 | mut self, 61 | authenticator_provider: Arc, 62 | ) -> Self { 63 | self.authenticator_provider = authenticator_provider; 64 | self 65 | } 66 | 67 | /// Adds initial node address (a contact point). Contact points are considered local to the 68 | /// driver until a topology refresh occurs. 69 | #[must_use] 70 | pub fn with_contact_point(mut self, addr: NodeAddress) -> Self { 71 | self.addrs.push(addr); 72 | self 73 | } 74 | 75 | /// Adds initial node addresses 76 | #[must_use] 77 | pub fn with_contact_points(mut self, addr: Vec) -> Self { 78 | self.addrs.extend(addr); 79 | self 80 | } 81 | 82 | /// Set cassandra protocol version 83 | #[must_use] 84 | pub fn with_version(mut self, version: Version) -> Self { 85 | self.version = version; 86 | self 87 | } 88 | 89 | /// Sets beta protocol usage flag 90 | #[must_use] 91 | pub fn with_beta_protocol(mut self, beta_protocol: bool) -> Self { 92 | self.beta_protocol = beta_protocol; 93 | self 94 | } 95 | 96 | /// Adds HTTP proxy configuration 97 | #[cfg(feature = "http-proxy")] 98 | #[must_use] 99 | pub fn with_http_proxy(mut self, config: HttpProxyConfig) -> Self { 100 | self.http_proxy = Some(config); 101 | self 102 | } 103 | 104 | /// Finalizes building process 105 | pub async fn build(self) -> Result { 106 | // replace with map() when async lambdas become available 107 | let mut contact_points = Vec::with_capacity(self.addrs.len()); 108 | for contact_point in self.addrs { 109 | contact_points.append(&mut contact_point.resolve_address().await?); 110 | } 111 | 112 | Ok(NodeRustlsConfig { 113 | contact_points, 114 | dns_name: self.dns_name, 115 | authenticator_provider: self.authenticator_provider, 116 | config: self.config, 117 | version: self.version, 118 | beta_protocol: self.beta_protocol, 119 | #[cfg(feature = "http-proxy")] 120 | http_proxy: self.http_proxy, 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_query.rs: -------------------------------------------------------------------------------- 1 | use crate::consistency::Consistency; 2 | use crate::error; 3 | use crate::frame::traits::FromCursor; 4 | use crate::frame::{Direction, Envelope, Flags, Opcode, Serialize, Version}; 5 | use crate::query::{QueryParams, QueryValues}; 6 | use crate::types::{from_cursor_str_long, serialize_str_long, CBytes, CInt, CLong, INT_LEN}; 7 | use std::io::Cursor; 8 | 9 | /// Structure which represents body of Query request 10 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 11 | pub struct BodyReqQuery { 12 | /// Query string. 13 | pub query: String, 14 | /// Query parameters. 15 | pub query_params: QueryParams, 16 | } 17 | 18 | impl BodyReqQuery { 19 | #[allow(clippy::too_many_arguments)] 20 | fn new( 21 | query: String, 22 | consistency: Consistency, 23 | values: Option, 24 | with_names: bool, 25 | page_size: Option, 26 | paging_state: Option, 27 | serial_consistency: Option, 28 | timestamp: Option, 29 | keyspace: Option, 30 | now_in_seconds: Option, 31 | ) -> BodyReqQuery { 32 | BodyReqQuery { 33 | query, 34 | query_params: QueryParams { 35 | consistency, 36 | with_names, 37 | values, 38 | page_size, 39 | paging_state, 40 | serial_consistency, 41 | timestamp, 42 | keyspace, 43 | now_in_seconds, 44 | }, 45 | } 46 | } 47 | } 48 | 49 | impl FromCursor for BodyReqQuery { 50 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 51 | let query = from_cursor_str_long(cursor)?.to_string(); 52 | let query_params = QueryParams::from_cursor(cursor, version)?; 53 | 54 | Ok(BodyReqQuery { 55 | query, 56 | query_params, 57 | }) 58 | } 59 | } 60 | 61 | impl Serialize for BodyReqQuery { 62 | #[inline] 63 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 64 | serialize_str_long(cursor, &self.query, version); 65 | self.query_params.serialize(cursor, version); 66 | } 67 | 68 | #[inline] 69 | fn serialize_to_vec(&self, version: Version) -> Vec { 70 | let mut buf = Vec::with_capacity(INT_LEN + self.query.len()); 71 | 72 | self.serialize(&mut Cursor::new(&mut buf), version); 73 | buf 74 | } 75 | } 76 | 77 | impl Envelope { 78 | #[allow(clippy::too_many_arguments)] 79 | pub fn new_req_query( 80 | query: String, 81 | consistency: Consistency, 82 | values: Option, 83 | with_names: bool, 84 | page_size: Option, 85 | paging_state: Option, 86 | serial_consistency: Option, 87 | timestamp: Option, 88 | keyspace: Option, 89 | now_in_seconds: Option, 90 | flags: Flags, 91 | version: Version, 92 | ) -> Envelope { 93 | let direction = Direction::Request; 94 | let opcode = Opcode::Query; 95 | let body = BodyReqQuery::new( 96 | query, 97 | consistency, 98 | values, 99 | with_names, 100 | page_size, 101 | paging_state, 102 | serial_consistency, 103 | timestamp, 104 | keyspace, 105 | now_in_seconds, 106 | ); 107 | 108 | Envelope::new( 109 | version, 110 | direction, 111 | flags, 112 | opcode, 113 | 0, 114 | body.serialize_to_vec(version), 115 | None, 116 | vec![], 117 | ) 118 | } 119 | 120 | #[inline] 121 | pub fn new_query(query: BodyReqQuery, flags: Flags, version: Version) -> Envelope { 122 | Envelope::new_req_query( 123 | query.query, 124 | query.query_params.consistency, 125 | query.query_params.values, 126 | query.query_params.with_names, 127 | query.query_params.page_size, 128 | query.query_params.paging_state, 129 | query.query_params.serial_consistency, 130 | query.query_params.timestamp, 131 | query.query_params.keyspace, 132 | query.query_params.now_in_seconds, 133 | flags, 134 | version, 135 | ) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_execute.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 3 | use crate::query::QueryParams; 4 | use crate::types::CBytesShort; 5 | use derive_more::Constructor; 6 | use std::io::Cursor; 7 | 8 | /// The structure that represents a body of a envelope of type `execute`. 9 | #[derive(Debug, Constructor, Eq, PartialEq, Clone)] 10 | pub struct BodyReqExecute<'a> { 11 | pub id: &'a CBytesShort, 12 | pub result_metadata_id: Option<&'a CBytesShort>, 13 | pub query_parameters: &'a QueryParams, 14 | } 15 | 16 | impl<'a> Serialize for BodyReqExecute<'a> { 17 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 18 | self.id.serialize(cursor, version); 19 | 20 | if let Some(result_metadata_id) = self.result_metadata_id { 21 | result_metadata_id.serialize(cursor, version); 22 | } 23 | 24 | self.query_parameters.serialize(cursor, version); 25 | } 26 | 27 | #[inline] 28 | fn serialize_to_vec(&self, version: Version) -> Vec { 29 | let mut buf = Vec::with_capacity( 30 | self.id.serialized_len() 31 | + self 32 | .result_metadata_id 33 | .map(|id| id.serialized_len()) 34 | .unwrap_or(0), 35 | ); 36 | 37 | self.serialize(&mut Cursor::new(&mut buf), version); 38 | buf 39 | } 40 | } 41 | 42 | /// The structure that represents an owned body of a envelope of type `execute`. 43 | #[derive(Debug, Constructor, Clone, Eq, PartialEq, Default)] 44 | pub struct BodyReqExecuteOwned { 45 | pub id: CBytesShort, 46 | pub result_metadata_id: Option, 47 | pub query_parameters: QueryParams, 48 | } 49 | 50 | impl FromCursor for BodyReqExecuteOwned { 51 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 52 | let id = CBytesShort::from_cursor(cursor, version)?; 53 | 54 | let result_metadata_id = if version >= Version::V5 { 55 | Some(CBytesShort::from_cursor(cursor, version)?) 56 | } else { 57 | None 58 | }; 59 | 60 | let query_parameters = QueryParams::from_cursor(cursor, version)?; 61 | 62 | Ok(BodyReqExecuteOwned::new( 63 | id, 64 | result_metadata_id, 65 | query_parameters, 66 | )) 67 | } 68 | } 69 | 70 | impl Serialize for BodyReqExecuteOwned { 71 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 72 | BodyReqExecute::new( 73 | &self.id, 74 | self.result_metadata_id.as_ref(), 75 | &self.query_parameters, 76 | ) 77 | .serialize(cursor, version); 78 | } 79 | } 80 | 81 | impl Envelope { 82 | pub fn new_req_execute( 83 | id: &CBytesShort, 84 | result_metadata_id: Option<&CBytesShort>, // only required for protocol >= V5 85 | query_parameters: &QueryParams, 86 | flags: Flags, 87 | version: Version, 88 | ) -> Envelope { 89 | let direction = Direction::Request; 90 | let opcode = Opcode::Execute; 91 | 92 | let body = BodyReqExecute::new(id, result_metadata_id, query_parameters); 93 | 94 | Envelope::new( 95 | version, 96 | direction, 97 | flags, 98 | opcode, 99 | 0, 100 | body.serialize_to_vec(version), 101 | None, 102 | vec![], 103 | ) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use crate::consistency::Consistency; 110 | use crate::frame::message_execute::BodyReqExecuteOwned; 111 | use crate::frame::traits::Serialize; 112 | use crate::frame::{FromCursor, Version}; 113 | use crate::query::QueryParams; 114 | use crate::types::CBytesShort; 115 | use std::io::Cursor; 116 | 117 | #[test] 118 | fn should_deserialize_body() { 119 | let data = [0, 1, 2, 0, 0, 0]; 120 | let mut cursor = Cursor::new(data.as_slice()); 121 | 122 | let body = BodyReqExecuteOwned::from_cursor(&mut cursor, Version::V4).unwrap(); 123 | assert_eq!(body.id, CBytesShort::new(vec![2])); 124 | assert_eq!(body.query_parameters.consistency, Consistency::Any); 125 | } 126 | 127 | #[test] 128 | fn should_support_result_metadata_id() { 129 | let body = BodyReqExecuteOwned::new( 130 | CBytesShort::new(vec![1]), 131 | Some(CBytesShort::new(vec![2])), 132 | QueryParams::default(), 133 | ); 134 | let data = body.serialize_to_vec(Version::V5); 135 | assert_eq!( 136 | BodyReqExecuteOwned::from_cursor(&mut Cursor::new(&data), Version::V5).unwrap(), 137 | body 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_startup.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 3 | use crate::types::{from_cursor_str, serialize_str, CIntShort}; 4 | use std::collections::HashMap; 5 | use std::io::Cursor; 6 | 7 | const CQL_VERSION: &str = "CQL_VERSION"; 8 | const CQL_VERSION_VAL: &str = "3.0.0"; 9 | const COMPRESSION: &str = "COMPRESSION"; 10 | const DRIVER_NAME: &str = "DRIVER_NAME"; 11 | const DRIVER_VERSION: &str = "DRIVER_VERSION"; 12 | 13 | #[derive(Debug, PartialEq, Eq, Default, Clone)] 14 | pub struct BodyReqStartup { 15 | pub map: HashMap, 16 | } 17 | 18 | impl BodyReqStartup { 19 | pub fn new(compression: Option, version: Version) -> BodyReqStartup { 20 | let mut map = HashMap::new(); 21 | map.insert(CQL_VERSION.into(), CQL_VERSION_VAL.into()); 22 | if let Some(c) = compression { 23 | map.insert(COMPRESSION.into(), c); 24 | } 25 | 26 | if version >= Version::V5 { 27 | map.insert(DRIVER_NAME.into(), "cdrs-tokio".into()); 28 | if let Some(version) = option_env!("CARGO_PKG_VERSION") { 29 | map.insert(DRIVER_VERSION.into(), version.into()); 30 | } 31 | } 32 | 33 | BodyReqStartup { map } 34 | } 35 | } 36 | 37 | impl Serialize for BodyReqStartup { 38 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 39 | let num = self.map.len() as CIntShort; 40 | num.serialize(cursor, version); 41 | 42 | for (key, val) in &self.map { 43 | serialize_str(cursor, key, version); 44 | serialize_str(cursor, val, version); 45 | } 46 | } 47 | } 48 | 49 | impl FromCursor for BodyReqStartup { 50 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 51 | let num = CIntShort::from_cursor(cursor, version)?; 52 | 53 | let mut map = HashMap::with_capacity(num as usize); 54 | for _ in 0..num { 55 | map.insert( 56 | from_cursor_str(cursor)?.to_string(), 57 | from_cursor_str(cursor)?.to_string(), 58 | ); 59 | } 60 | 61 | Ok(BodyReqStartup { map }) 62 | } 63 | } 64 | 65 | impl Envelope { 66 | /// Creates new envelope of type `startup`. 67 | pub fn new_req_startup(compression: Option, version: Version) -> Envelope { 68 | let direction = Direction::Request; 69 | let opcode = Opcode::Startup; 70 | let body = BodyReqStartup::new(compression, version); 71 | 72 | Envelope::new( 73 | version, 74 | direction, 75 | Flags::empty(), 76 | opcode, 77 | 0, 78 | body.serialize_to_vec(version), 79 | None, 80 | vec![], 81 | ) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod test { 87 | use super::*; 88 | use crate::frame::{Envelope, Flags, Opcode, Version}; 89 | 90 | #[test] 91 | fn new_body_req_startup_some_compression() { 92 | let compression = "test_compression"; 93 | let body = BodyReqStartup::new(Some(compression.into()), Version::V4); 94 | assert_eq!( 95 | body.map.get("CQL_VERSION"), 96 | Some("3.0.0".to_string()).as_ref() 97 | ); 98 | assert_eq!( 99 | body.map.get("COMPRESSION"), 100 | Some(compression.to_string()).as_ref() 101 | ); 102 | assert_eq!(body.map.len(), 2); 103 | } 104 | 105 | #[test] 106 | fn new_body_req_startup_none_compression() { 107 | let body = BodyReqStartup::new(None, Version::V4); 108 | assert_eq!( 109 | body.map.get("CQL_VERSION"), 110 | Some("3.0.0".to_string()).as_ref() 111 | ); 112 | assert_eq!(body.map.len(), 1); 113 | } 114 | 115 | #[test] 116 | fn new_req_startup() { 117 | let compression = Some("test_compression".to_string()); 118 | let frame = Envelope::new_req_startup(compression, Version::V4); 119 | assert_eq!(frame.version, Version::V4); 120 | assert_eq!(frame.flags, Flags::empty()); 121 | assert_eq!(frame.opcode, Opcode::Startup); 122 | assert_eq!(frame.tracing_id, None); 123 | assert!(frame.warnings.is_empty()); 124 | } 125 | 126 | #[test] 127 | fn body_req_startup_from_cursor() { 128 | let bytes = vec![ 129 | 0, 3, 0, 11, 68, 82, 73, 86, 69, 82, 95, 78, 65, 77, 69, 0, 22, 68, 97, 116, 97, 83, 130 | 116, 97, 120, 32, 80, 121, 116, 104, 111, 110, 32, 68, 114, 105, 118, 101, 114, 0, 14, 131 | 68, 82, 73, 86, 69, 82, 95, 86, 69, 82, 83, 73, 79, 78, 0, 6, 51, 46, 50, 53, 46, 48, 132 | 0, 11, 67, 81, 76, 95, 86, 69, 82, 83, 73, 79, 78, 0, 5, 51, 46, 52, 46, 53, 133 | ]; 134 | 135 | let mut cursor = Cursor::new(bytes.as_slice()); 136 | BodyReqStartup::from_cursor(&mut cursor, Version::V4).unwrap(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /cassandra-protocol/src/query/batch_query_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::consistency::Consistency; 2 | use crate::error::{Error as CError, Result as CResult}; 3 | use crate::frame::message_batch::{BatchQuery, BatchQuerySubj, BatchType, BodyReqBatch}; 4 | use crate::query::{PreparedQuery, QueryValues}; 5 | use crate::types::{CBytesShort, CInt, CLong}; 6 | use derivative::Derivative; 7 | use derive_more::Constructor; 8 | use std::collections::HashMap; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Constructor)] 11 | pub struct QueryBatchPreparedStatement { 12 | pub query: String, 13 | pub keyspace: Option, 14 | } 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq, Constructor, Derivative)] 17 | pub struct QueryBatch { 18 | pub request: BodyReqBatch, 19 | #[derivative(Debug = "ignore")] 20 | pub prepared_queries: HashMap, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct BatchQueryBuilder { 25 | batch_type: BatchType, 26 | queries: Vec, 27 | prepared_queries: HashMap, 28 | consistency: Consistency, 29 | serial_consistency: Option, 30 | timestamp: Option, 31 | keyspace: Option, 32 | now_in_seconds: Option, 33 | } 34 | 35 | impl Default for BatchQueryBuilder { 36 | fn default() -> Self { 37 | BatchQueryBuilder { 38 | batch_type: BatchType::Logged, 39 | queries: vec![], 40 | prepared_queries: HashMap::new(), 41 | consistency: Consistency::One, 42 | serial_consistency: None, 43 | timestamp: None, 44 | keyspace: None, 45 | now_in_seconds: None, 46 | } 47 | } 48 | } 49 | 50 | impl BatchQueryBuilder { 51 | pub fn new() -> BatchQueryBuilder { 52 | Default::default() 53 | } 54 | 55 | #[must_use] 56 | pub fn with_batch_type(mut self, batch_type: BatchType) -> Self { 57 | self.batch_type = batch_type; 58 | self 59 | } 60 | 61 | /// Add a query (non-prepared one) 62 | #[must_use] 63 | pub fn add_query>(mut self, query: T, values: QueryValues) -> Self { 64 | self.queries.push(BatchQuery { 65 | subject: BatchQuerySubj::QueryString(query.into()), 66 | values, 67 | }); 68 | self 69 | } 70 | 71 | /// Add a query (prepared one) 72 | #[must_use] 73 | pub fn add_query_prepared(mut self, query: &PreparedQuery, values: QueryValues) -> Self { 74 | self.queries.push(BatchQuery { 75 | subject: BatchQuerySubj::PreparedId(query.id.clone()), 76 | values, 77 | }); 78 | self.prepared_queries.insert( 79 | query.id.clone(), 80 | QueryBatchPreparedStatement::new(query.query.clone(), query.keyspace.clone()), 81 | ); 82 | 83 | self 84 | } 85 | 86 | #[must_use] 87 | pub fn clear_queries(mut self) -> Self { 88 | self.queries = vec![]; 89 | self 90 | } 91 | 92 | #[must_use] 93 | pub fn with_consistency(mut self, consistency: Consistency) -> Self { 94 | self.consistency = consistency; 95 | self 96 | } 97 | 98 | #[must_use] 99 | pub fn with_serial_consistency(mut self, serial_consistency: Consistency) -> Self { 100 | self.serial_consistency = Some(serial_consistency); 101 | self 102 | } 103 | 104 | #[must_use] 105 | pub fn with_timestamp(mut self, timestamp: CLong) -> Self { 106 | self.timestamp = Some(timestamp); 107 | self 108 | } 109 | 110 | #[must_use] 111 | pub fn with_keyspace(mut self, keyspace: String) -> Self { 112 | self.keyspace = Some(keyspace); 113 | self 114 | } 115 | 116 | #[must_use] 117 | pub fn with_now_in_seconds(mut self, now_in_seconds: CInt) -> Self { 118 | self.now_in_seconds = Some(now_in_seconds); 119 | self 120 | } 121 | 122 | pub fn build(self) -> CResult { 123 | let with_names_for_values = self.queries.iter().all(|q| q.values.has_names()); 124 | 125 | if !with_names_for_values { 126 | let some_names_for_values = self.queries.iter().any(|q| q.values.has_names()); 127 | 128 | if some_names_for_values { 129 | return Err(CError::General(String::from( 130 | "Inconsistent query values - mixed with and without names values", 131 | ))); 132 | } 133 | } 134 | 135 | Ok(QueryBatch::new( 136 | BodyReqBatch { 137 | batch_type: self.batch_type, 138 | queries: self.queries, 139 | consistency: self.consistency, 140 | serial_consistency: self.serial_consistency, 141 | timestamp: self.timestamp, 142 | keyspace: self.keyspace, 143 | now_in_seconds: self.now_in_seconds, 144 | }, 145 | self.prepared_queries, 146 | )) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /cassandra-protocol/src/types/rows.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use std::num::{NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8}; 3 | use std::sync::Arc; 4 | 5 | use chrono::prelude::*; 6 | use time::PrimitiveDateTime; 7 | use uuid::Uuid; 8 | 9 | use crate::error::{column_is_empty_err, Error, Result}; 10 | use crate::frame::message_result::{ 11 | BodyResResultRows, ColSpec, ColType, ColTypeOption, ColTypeOptionValue, RowsMetadata, 12 | }; 13 | use crate::frame::Version; 14 | use crate::types::blob::Blob; 15 | use crate::types::data_serialization_types::*; 16 | use crate::types::decimal::Decimal; 17 | use crate::types::list::List; 18 | use crate::types::map::Map; 19 | use crate::types::tuple::Tuple; 20 | use crate::types::udt::Udt; 21 | use crate::types::{ByIndex, ByName, CBytes, IntoRustByIndex, IntoRustByName}; 22 | use num_bigint::BigInt; 23 | 24 | #[derive(Clone, Debug)] 25 | pub struct Row { 26 | metadata: Arc, 27 | row_content: Vec, 28 | protocol_version: Version, 29 | } 30 | 31 | impl Row { 32 | pub fn from_body(body: BodyResResultRows) -> Vec { 33 | let metadata = Arc::new(body.metadata); 34 | let protocol_version = body.protocol_version; 35 | body.rows_content 36 | .into_iter() 37 | .map(|row| Row { 38 | metadata: metadata.clone(), 39 | row_content: row, 40 | protocol_version, 41 | }) 42 | .collect() 43 | } 44 | 45 | /// Checks if a column is present in the row. 46 | pub fn contains_column(&self, name: &str) -> bool { 47 | self.metadata 48 | .col_specs 49 | .iter() 50 | .any(|spec| spec.name.as_str() == name) 51 | } 52 | 53 | /// Checks for NULL for a given column. Returns false if given column does not exist. 54 | pub fn is_empty(&self, index: usize) -> bool { 55 | self.row_content 56 | .get(index) 57 | .map(|data| data.is_null_or_empty()) 58 | .unwrap_or(false) 59 | } 60 | 61 | /// Checks for NULL for a given column. Returns false if given column does not exist. 62 | pub fn is_empty_by_name(&self, name: &str) -> bool { 63 | self.metadata 64 | .col_specs 65 | .iter() 66 | .position(|spec| spec.name.as_str() == name) 67 | .map(|index| self.is_empty(index)) 68 | .unwrap_or(false) 69 | } 70 | 71 | fn col_spec_by_name(&self, name: &str) -> Option<(&ColSpec, &CBytes)> { 72 | self.metadata 73 | .col_specs 74 | .iter() 75 | .position(|spec| spec.name.as_str() == name) 76 | .map(|i| { 77 | let col_spec = &self.metadata.col_specs[i]; 78 | let data = &self.row_content[i]; 79 | (col_spec, data) 80 | }) 81 | } 82 | 83 | fn col_spec_by_index(&self, index: usize) -> Option<(&ColSpec, &CBytes)> { 84 | let specs = self.metadata.col_specs.iter(); 85 | let values = self.row_content.iter(); 86 | specs.zip(values).nth(index) 87 | } 88 | } 89 | 90 | impl ByName for Row {} 91 | 92 | into_rust_by_name!(Row, Blob); 93 | into_rust_by_name!(Row, String); 94 | into_rust_by_name!(Row, bool); 95 | into_rust_by_name!(Row, i64); 96 | into_rust_by_name!(Row, i32); 97 | into_rust_by_name!(Row, i16); 98 | into_rust_by_name!(Row, i8); 99 | into_rust_by_name!(Row, f64); 100 | into_rust_by_name!(Row, f32); 101 | into_rust_by_name!(Row, IpAddr); 102 | into_rust_by_name!(Row, Uuid); 103 | into_rust_by_name!(Row, List); 104 | into_rust_by_name!(Row, Map); 105 | into_rust_by_name!(Row, Udt); 106 | into_rust_by_name!(Row, Tuple); 107 | into_rust_by_name!(Row, PrimitiveDateTime); 108 | into_rust_by_name!(Row, Decimal); 109 | into_rust_by_name!(Row, NonZeroI8); 110 | into_rust_by_name!(Row, NonZeroI16); 111 | into_rust_by_name!(Row, NonZeroI32); 112 | into_rust_by_name!(Row, NonZeroI64); 113 | into_rust_by_name!(Row, NaiveDateTime); 114 | into_rust_by_name!(Row, DateTime); 115 | into_rust_by_name!(Row, BigInt); 116 | 117 | impl ByIndex for Row {} 118 | 119 | into_rust_by_index!(Row, Blob); 120 | into_rust_by_index!(Row, String); 121 | into_rust_by_index!(Row, bool); 122 | into_rust_by_index!(Row, i64); 123 | into_rust_by_index!(Row, i32); 124 | into_rust_by_index!(Row, i16); 125 | into_rust_by_index!(Row, i8); 126 | into_rust_by_index!(Row, f64); 127 | into_rust_by_index!(Row, f32); 128 | into_rust_by_index!(Row, IpAddr); 129 | into_rust_by_index!(Row, Uuid); 130 | into_rust_by_index!(Row, List); 131 | into_rust_by_index!(Row, Map); 132 | into_rust_by_index!(Row, Udt); 133 | into_rust_by_index!(Row, Tuple); 134 | into_rust_by_index!(Row, PrimitiveDateTime); 135 | into_rust_by_index!(Row, Decimal); 136 | into_rust_by_index!(Row, NonZeroI8); 137 | into_rust_by_index!(Row, NonZeroI16); 138 | into_rust_by_index!(Row, NonZeroI32); 139 | into_rust_by_index!(Row, NonZeroI64); 140 | into_rust_by_index!(Row, NaiveDateTime); 141 | into_rust_by_index!(Row, DateTime); 142 | into_rust_by_index!(Row, BigInt); 143 | -------------------------------------------------------------------------------- /cassandra-protocol/src/authenticators.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::types::CBytes; 3 | 4 | /// Handles SASL authentication. 5 | /// 6 | /// The lifecycle of an authenticator consists of: 7 | /// - The `initial_response` function will be called. The initial return value will be sent to the 8 | /// server to initiate the handshake. 9 | /// - The server will respond to each client response by either issuing a challenge or indicating 10 | /// that the authentication is complete (successfully or not). If a new challenge is issued, 11 | /// the authenticator's `evaluate_challenge` function will be called to produce a response 12 | /// that will be sent to the server. This challenge/response negotiation will continue until 13 | /// the server responds that authentication is successful or an error is raised. 14 | /// - On success, the `handle_success` will be called with data returned by the server. 15 | pub trait SaslAuthenticator { 16 | fn initial_response(&self) -> CBytes; 17 | 18 | fn evaluate_challenge(&self, challenge: CBytes) -> Result; 19 | 20 | fn handle_success(&self, data: CBytes) -> Result<()>; 21 | } 22 | 23 | /// Provides authenticators per new connection. 24 | pub trait SaslAuthenticatorProvider { 25 | fn name(&self) -> Option<&str>; 26 | 27 | fn create_authenticator(&self) -> Box; 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub struct StaticPasswordAuthenticator { 32 | username: String, 33 | password: String, 34 | } 35 | 36 | impl StaticPasswordAuthenticator { 37 | pub fn new(username: S, password: S) -> StaticPasswordAuthenticator { 38 | StaticPasswordAuthenticator { 39 | username: username.to_string(), 40 | password: password.to_string(), 41 | } 42 | } 43 | } 44 | 45 | impl SaslAuthenticator for StaticPasswordAuthenticator { 46 | fn initial_response(&self) -> CBytes { 47 | let mut token = vec![0]; 48 | token.extend_from_slice(self.username.as_bytes()); 49 | token.push(0); 50 | token.extend_from_slice(self.password.as_bytes()); 51 | 52 | CBytes::new(token) 53 | } 54 | 55 | fn evaluate_challenge(&self, _challenge: CBytes) -> Result { 56 | Err("Server challenge is not supported for StaticPasswordAuthenticator!".into()) 57 | } 58 | 59 | fn handle_success(&self, _data: CBytes) -> Result<()> { 60 | Ok(()) 61 | } 62 | } 63 | 64 | /// Authentication provider with a username and password. 65 | #[derive(Debug, Clone)] 66 | pub struct StaticPasswordAuthenticatorProvider { 67 | username: String, 68 | password: String, 69 | } 70 | 71 | impl SaslAuthenticatorProvider for StaticPasswordAuthenticatorProvider { 72 | fn name(&self) -> Option<&str> { 73 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 74 | } 75 | 76 | fn create_authenticator(&self) -> Box { 77 | Box::new(StaticPasswordAuthenticator::new( 78 | self.username.clone(), 79 | self.password.clone(), 80 | )) 81 | } 82 | } 83 | 84 | impl StaticPasswordAuthenticatorProvider { 85 | pub fn new(username: S, password: S) -> Self { 86 | StaticPasswordAuthenticatorProvider { 87 | username: username.to_string(), 88 | password: password.to_string(), 89 | } 90 | } 91 | } 92 | 93 | #[derive(Debug, Clone)] 94 | pub struct NoneAuthenticator; 95 | 96 | impl SaslAuthenticator for NoneAuthenticator { 97 | fn initial_response(&self) -> CBytes { 98 | CBytes::new(vec![0]) 99 | } 100 | 101 | fn evaluate_challenge(&self, _challenge: CBytes) -> Result { 102 | Err("Server challenge is not supported for NoneAuthenticator!".into()) 103 | } 104 | 105 | fn handle_success(&self, _data: CBytes) -> Result<()> { 106 | Ok(()) 107 | } 108 | } 109 | 110 | /// Provider for no authentication. 111 | #[derive(Debug, Clone)] 112 | pub struct NoneAuthenticatorProvider; 113 | 114 | impl SaslAuthenticatorProvider for NoneAuthenticatorProvider { 115 | fn name(&self) -> Option<&str> { 116 | None 117 | } 118 | 119 | fn create_authenticator(&self) -> Box { 120 | Box::new(NoneAuthenticator) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | #[test] 129 | fn test_static_password_authenticator_new() { 130 | StaticPasswordAuthenticator::new("foo", "bar"); 131 | } 132 | 133 | #[test] 134 | fn test_static_password_authenticator_cassandra_name() { 135 | let auth = StaticPasswordAuthenticatorProvider::new("foo", "bar"); 136 | assert_eq!( 137 | auth.name(), 138 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 139 | ); 140 | } 141 | 142 | #[test] 143 | fn test_authenticator_none_cassandra_name() { 144 | let auth = NoneAuthenticator; 145 | let provider = NoneAuthenticatorProvider; 146 | assert_eq!(provider.name(), None); 147 | assert_eq!(auth.initial_response().into_bytes().unwrap(), vec![0]); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /cassandra-protocol/src/frame/message_prepare.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::frame::{Direction, Envelope, Flags, FromCursor, Opcode, Serialize, Version}; 3 | use crate::query::PrepareFlags; 4 | use crate::types::{ 5 | from_cursor_str, from_cursor_str_long, serialize_str, serialize_str_long, INT_LEN, SHORT_LEN, 6 | }; 7 | use std::io::Cursor; 8 | 9 | /// Struct that represents a body of a envelope of type `prepare` 10 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Default)] 11 | pub struct BodyReqPrepare { 12 | pub query: String, 13 | pub keyspace: Option, 14 | } 15 | 16 | impl BodyReqPrepare { 17 | /// Creates new body of a envelope of type `prepare` that prepares query `query`. 18 | #[inline] 19 | pub fn new(query: String, keyspace: Option) -> BodyReqPrepare { 20 | BodyReqPrepare { query, keyspace } 21 | } 22 | } 23 | 24 | impl Serialize for BodyReqPrepare { 25 | #[inline] 26 | fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) { 27 | serialize_str_long(cursor, &self.query, version); 28 | 29 | if version >= Version::V5 { 30 | if let Some(keyspace) = &self.keyspace { 31 | PrepareFlags::WITH_KEYSPACE.serialize(cursor, version); 32 | serialize_str(cursor, keyspace.as_str(), version); 33 | } else { 34 | PrepareFlags::empty().serialize(cursor, version); 35 | } 36 | } 37 | } 38 | 39 | #[inline] 40 | fn serialize_to_vec(&self, version: Version) -> Vec { 41 | let mut buf = if version >= Version::V5 { 42 | Vec::with_capacity( 43 | INT_LEN * 2 44 | + self.query.len() 45 | + self 46 | .keyspace 47 | .as_ref() 48 | .map(|keyspace| SHORT_LEN + keyspace.len()) 49 | .unwrap_or(0), 50 | ) 51 | } else { 52 | Vec::with_capacity(INT_LEN + self.query.len()) 53 | }; 54 | 55 | self.serialize(&mut Cursor::new(&mut buf), version); 56 | buf 57 | } 58 | } 59 | 60 | impl FromCursor for BodyReqPrepare { 61 | #[inline] 62 | fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result { 63 | if version >= Version::V5 { 64 | from_cursor_str_long(cursor) 65 | .and_then(|query| { 66 | PrepareFlags::from_cursor(cursor, version).map(|flags| (query, flags)) 67 | }) 68 | .and_then(|(query, flags)| { 69 | if flags.contains(PrepareFlags::WITH_KEYSPACE) { 70 | from_cursor_str(cursor).map(|keyspace| { 71 | BodyReqPrepare::new(query.into(), Some(keyspace.into())) 72 | }) 73 | } else { 74 | Ok(BodyReqPrepare::new(query.into(), None)) 75 | } 76 | }) 77 | } else { 78 | from_cursor_str_long(cursor).map(|query| BodyReqPrepare::new(query.into(), None)) 79 | } 80 | } 81 | } 82 | 83 | impl Envelope { 84 | pub fn new_req_prepare( 85 | query: String, 86 | keyspace: Option, 87 | flags: Flags, 88 | version: Version, 89 | ) -> Envelope { 90 | let direction = Direction::Request; 91 | let opcode = Opcode::Prepare; 92 | let body = BodyReqPrepare::new(query, keyspace); 93 | 94 | Envelope::new( 95 | version, 96 | direction, 97 | flags, 98 | opcode, 99 | 0, 100 | body.serialize_to_vec(version), 101 | None, 102 | vec![], 103 | ) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use crate::frame::message_prepare::BodyReqPrepare; 110 | use crate::frame::{FromCursor, Serialize, Version}; 111 | use std::io::Cursor; 112 | 113 | #[test] 114 | fn should_deserialize_body() { 115 | let data = [0, 0, 0, 3, 102, 111, 111, 0]; 116 | let mut cursor = Cursor::new(data.as_slice()); 117 | 118 | let body = BodyReqPrepare::from_cursor(&mut cursor, Version::V4).unwrap(); 119 | assert_eq!(body.query, "foo"); 120 | } 121 | 122 | #[test] 123 | fn should_support_keyspace() { 124 | let keyspace = "abc"; 125 | let query = "test"; 126 | 127 | let body = BodyReqPrepare::new(query.into(), Some(keyspace.into())); 128 | 129 | let data_v4 = body.serialize_to_vec(Version::V4); 130 | let body_v4 = 131 | BodyReqPrepare::from_cursor(&mut Cursor::new(data_v4.as_slice()), Version::V4).unwrap(); 132 | assert_eq!(body_v4.query, query); 133 | assert_eq!(body_v4.keyspace, None); 134 | 135 | let data_v5 = body.serialize_to_vec(Version::V5); 136 | let body_v5 = 137 | BodyReqPrepare::from_cursor(&mut Cursor::new(data_v5.as_slice()), Version::V5).unwrap(); 138 | assert_eq!(body_v5.query, query); 139 | assert_eq!(body_v5.keyspace, Some(keyspace.to_string())); 140 | } 141 | } 142 | --------------------------------------------------------------------------------