├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── benchmarks.md ├── cassandra-ports.txt ├── cdrs-logo.png ├── changelog.md ├── documentation ├── README.md ├── batching-multiple-queries.md ├── cdrs-session.md ├── cluster-configuration.md ├── deserialization.md ├── making-query.md ├── preparing-and-executing-queries.md ├── query-values.md └── schemes │ └── cdrs-load-balancing.png ├── examples ├── README.md ├── crud_operations.rs ├── dynamic_cluster.rs ├── insert_collection.rs ├── multiple_thread.rs ├── paged_query.rs ├── prepare_batch_execute.rs └── server_events.rs ├── fixtures └── frame │ └── events │ ├── schema-change-created-keyspace │ ├── status-change-down │ ├── status-change-up │ ├── topology-change-new-node │ └── topology-change-removed-node ├── src ├── authenticators.rs ├── cluster │ ├── config_rustls.rs │ ├── config_ssl.rs │ ├── config_tcp.rs │ ├── generic_connection_pool.rs │ ├── mod.rs │ ├── pager.rs │ ├── rustls_connection_pool.rs │ ├── session.rs │ ├── ssl_connection_pool.rs │ └── tcp_connection_pool.rs ├── compression.rs ├── consistency.rs ├── error.rs ├── events.rs ├── frame │ ├── events.rs │ ├── frame_auth_challenge.rs │ ├── frame_auth_response.rs │ ├── frame_auth_success.rs │ ├── frame_authenticate.rs │ ├── frame_batch.rs │ ├── frame_error.rs │ ├── frame_event.rs │ ├── frame_execute.rs │ ├── frame_options.rs │ ├── frame_prepare.rs │ ├── frame_query.rs │ ├── frame_ready.rs │ ├── frame_register.rs │ ├── frame_response.rs │ ├── frame_result.rs │ ├── frame_startup.rs │ ├── frame_supported.rs │ ├── mod.rs │ ├── parser.rs │ └── traits.rs ├── lib.rs ├── load_balancing │ ├── mod.rs │ ├── random.rs │ ├── round_robin.rs │ ├── round_robin_sync.rs │ └── single_node.rs ├── macros.rs ├── query │ ├── batch_executor.rs │ ├── batch_query_builder.rs │ ├── exec_executor.rs │ ├── mod.rs │ ├── prepare_executor.rs │ ├── prepared_query.rs │ ├── query.rs │ ├── query_executor.rs │ ├── query_flags.rs │ ├── query_params.rs │ ├── query_params_builder.rs │ ├── query_values.rs │ └── utils.rs ├── test.rs ├── transport.rs └── types │ ├── blob.rs │ ├── data_serialization_types.rs │ ├── decimal.rs │ ├── from_cdrs.rs │ ├── list.rs │ ├── map.rs │ ├── mod.rs │ ├── rows.rs │ ├── tuple.rs │ ├── udt.rs │ └── value.rs ├── tests ├── build-cluster.sh ├── collection_types.rs ├── common │ └── mod.rs ├── derive_traits.rs ├── keyspace.rs ├── native_types.rs ├── query_values.rs ├── scylla-cluster.sh ├── tuple_types.rs └── user_defined_types.rs └── type-mapping.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: alexpikalov 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | *.bk 4 | .idea/ 5 | cdrs.iml 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | services: 11 | - cassandra 12 | sudo: required 13 | 14 | install: 15 | - PATH=$PATH:/home/travis/.cargo/bin 16 | 17 | script: 18 | # - cargo fmt -- --check 19 | - cargo build --verbose 20 | - cargo test --verbose --no-fail-fast 21 | - cargo test --verbose --no-fail-fast --features e2e-tests 22 | - cargo test --no-fail-fast --features ssl 23 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Please add yourself when you contributing first time. 2 | 3 | List of contributors: 4 | 5 | * AlexPikalov 6 | * harrydevnull 7 | * j4in 8 | * ernestas-poskus 9 | * dfaust 10 | * Dylan DPC 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cdrs" 3 | version = "4.0.0-beta.1" 4 | authors = ["Alex Pikalov "] 5 | edition = "2018" 6 | 7 | description = "Cassandra DB driver written in Rust" 8 | documentation = "https://docs.rs/cdrs" 9 | homepage = "https://github.com/AlexPikalov/cdrs" 10 | repository = "https://github.com/AlexPikalov/cdrs" 11 | readme = "./README.md" 12 | keywords = ["cassandra", "driver", "client", "cassandradb", "DB"] 13 | license = "MIT/Apache-2.0" 14 | 15 | [features] 16 | default = ["v4"] 17 | ssl = ["openssl"] 18 | rust-tls = ["rustls", "webpki"] 19 | v3 = [] 20 | v4 = [] 21 | # enable v5 feature when it's actually implemented 22 | # v5 = [] 23 | e2e-tests = [] 24 | # enables dynamic cluster adjustments basing on status 25 | # changes server events 26 | unstable-dynamic-cluster = [] 27 | 28 | [dependencies] 29 | byteorder = "1" 30 | log = "0.4.1" 31 | lz4-compress = "=0.1.0" 32 | openssl = { version = "0.10", optional = true } 33 | r2d2 = "0.8.7" 34 | rand = "0.4.1" 35 | snap = "0.2.3" 36 | time = "0.2.16" 37 | uuid = "0.8.1" 38 | webpki = { version = "0.21", optional = true } 39 | 40 | [dependencies.rustls] 41 | version = "0.17" 42 | optional = true 43 | default-features = false 44 | 45 | [dev-dependencies] 46 | env_logger = "0.4.3" 47 | maplit = "1.0.0" 48 | regex = "0.2.5" 49 | cdrs_helpers_derive = "0.4" 50 | 51 | [[example]] 52 | name = "dynamic_cluster" 53 | required-features = ["unstable-dynamic-cluster"] 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDRS [![crates.io version](https://img.shields.io/crates/v/cdrs.svg)](https://crates.io/crates/cdrs) [![Build Status](https://travis-ci.org/AlexPikalov/cdrs.svg?branch=master)](https://travis-ci.org/AlexPikalov/cdrs) [![Build status](https://ci.appveyor.com/api/projects/status/sirj4flws6o0dvb7/branch/master?svg=true)](https://ci.appveyor.com/project/harrydevnull/cdrs/branch/master) 2 | 3 | ## CDRS is looking for maintainers 4 | 5 |

6 | CDRS - Apache Cassandra driver 7 |

8 | 9 | CDRS is Apache **C**assandra **d**river written in pure **R**u**s**t. 10 | 11 | 💡Looking for an async version? 12 | - async-std https://github.com/AlexPikalov/cdrs-async (API is not fully compatible with https://github.com/AlexPikalov/cdrs) 13 | - tokio https://github.com/AlexPikalov/cdrs/tree/async-tokio 14 | 15 | ## Features 16 | 17 | - TCP/SSL connection; 18 | - Load balancing; 19 | - Connection pooling; 20 | - LZ4, Snappy compression; 21 | - Cassandra-to-Rust data deserialization; 22 | - Pluggable authentication strategies; 23 | - [ScyllaDB](https://www.scylladb.com/) support; 24 | - Server events listening; 25 | - Multiple CQL version support (3, 4), full spec implementation; 26 | - Query tracing information. 27 | 28 | ## Documentation and examples 29 | 30 | - [User guide](./documentation). 31 | - [Examples](./examples). 32 | - API docs (release). 33 | - Using ScyllaDB with RUST [lesson](https://university.scylladb.com/courses/using-scylla-drivers/lessons/rust-and-scylla/). 34 | 35 | ## Getting started 36 | 37 | Add CDRS to your `Cargo.toml` file as a dependency: 38 | 39 | ```toml 40 | cdrs = { version = "2" } 41 | ``` 42 | 43 | Then add it as an external crate to your `main.rs`: 44 | 45 | ```rust 46 | extern crate cdrs; 47 | 48 | use cdrs::authenticators::NoneAuthenticator; 49 | use cdrs::cluster::session::{new as new_session}; 50 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder}; 51 | use cdrs::load_balancing::RoundRobin; 52 | use cdrs::query::*; 53 | 54 | fn main() { 55 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 56 | let cluster_config = ClusterTcpConfig(vec![node]); 57 | let no_compression = 58 | new_session(&cluster_config, RoundRobin::new()).expect("session should be created"); 59 | 60 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 61 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 62 | no_compression.query(create_ks).expect("Keyspace create error"); 63 | } 64 | ``` 65 | 66 | This example configures a cluster consisting of a single node, and uses round robin load balancing and default `r2d2` values for connection pool. 67 | 68 | ## License 69 | 70 | This project is licensed under either of 71 | 72 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 73 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 74 | 75 | at your option. 76 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust using rustup for Rust installation 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | ## Build Matrix ## 10 | 11 | # This configuration will setup a build for each channel & target combination (12 windows 12 | # combinations in all). 13 | # 14 | # There are 3 channels: stable, beta, and nightly. 15 | # 16 | # Alternatively, the full version may be specified for the channel to build using that specific 17 | # version (e.g. channel: 1.5.0) 18 | # 19 | # The values for target are the set of windows Rust build targets. Each value is of the form 20 | # 21 | # ARCH-pc-windows-TOOLCHAIN 22 | # 23 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 24 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 25 | # a description of the toolchain differences. 26 | # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of 27 | # toolchains and host triples. 28 | # 29 | # Comment out channel/target combos you do not wish to build in CI. 30 | # 31 | # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands 32 | # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly 33 | # channels to enable unstable features when building for nightly. Or you could add additional 34 | # matrix entries to test different combinations of features. 35 | environment: 36 | matrix: 37 | 38 | ### MSVC Toolchains ### 39 | 40 | # Stable 64-bit MSVC 41 | - channel: stable 42 | target: x86_64-pc-windows-msvc 43 | cassandra_version: 3.0.5 44 | 45 | # Stable 32-bit MSVC 46 | - channel: stable 47 | target: i686-pc-windows-msvc 48 | # Beta 64-bit MSVC 49 | - channel: beta 50 | target: x86_64-pc-windows-msvc 51 | # Beta 32-bit MSVC 52 | - channel: beta 53 | target: i686-pc-windows-msvc 54 | # Nightly 64-bit MSVC 55 | - channel: nightly 56 | target: x86_64-pc-windows-msvc 57 | #cargoflags: --features "unstable" 58 | # Nightly 32-bit MSVC 59 | - channel: nightly 60 | target: i686-pc-windows-msvc 61 | cargoflags: --features "unstable" 62 | 63 | ### GNU Toolchains ### 64 | 65 | # # Stable 64-bit GNU 66 | # - channel: stable 67 | # target: x86_64-pc-windows-gnu 68 | # # Stable 32-bit GNU 69 | # - channel: stable 70 | # target: i686-pc-windows-gnu 71 | # # Beta 64-bit GNU 72 | # - channel: beta 73 | # target: x86_64-pc-windows-gnu 74 | # # Beta 32-bit GNU 75 | # - channel: beta 76 | # target: i686-pc-windows-gnu 77 | # # Nightly 64-bit GNU 78 | # - channel: nightly 79 | # target: x86_64-pc-windows-gnu 80 | # #cargoflags: --features "unstable" 81 | # # Nightly 32-bit GNU 82 | # - channel: nightly 83 | # target: i686-pc-windows-gnu 84 | #cargoflags: --features "unstable" 85 | 86 | ### Allowed failures ### 87 | 88 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 89 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 90 | # or test failure in the matching channels/targets from failing the entire build. 91 | matrix: 92 | allow_failures: 93 | - channel: nightly 94 | 95 | # If you only care about stable channel build failures, uncomment the following line: 96 | #- channel: beta 97 | 98 | ## Install Script ## 99 | 100 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 101 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 102 | # rustup to install Rust. 103 | # 104 | # For simple configurations, instead of using the build matrix, you can simply set the 105 | # default-toolchain and default-host manually here. 106 | install: 107 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 108 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 109 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 110 | - rustc -vV 111 | - cargo -vV 112 | 113 | 114 | ## Build Script ## 115 | 116 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 117 | # the "directory does not contain a project or solution file" error. 118 | build: false 119 | 120 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 121 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 122 | # environment variable. 123 | test_script: 124 | - cargo test --verbose %cargoflags% -------------------------------------------------------------------------------- /benchmarks.md: -------------------------------------------------------------------------------- 1 | ## CDRS performance benchmarks 2 | 3 | These benchmarks contain two type of measurements: 4 | 5 | - performance during parsing a frame that contains one row of values of a 6 | certain type 7 | - performance during type conversion when you try to convert a row into 8 | rust types/structures 9 | 10 | #### Native types 11 | 12 | ``` 13 | test blob_body_parse ... bench: 1,595 ns/iter (+/- 238) 14 | test blob_convert ... bench: 212 ns/iter (+/- 115) 15 | test counter_body_parse ... bench: 2,124 ns/iter (+/- 1,160) 16 | test counter_convert ... bench: 396 ns/iter (+/- 257) 17 | test float_body_parse ... bench: 1,908 ns/iter (+/- 310) 18 | test float_convert ... bench: 381 ns/iter (+/- 167) 19 | test inet_body_parse ... bench: 2,040 ns/iter (+/- 293) 20 | test inet_convert ... bench: 387 ns/iter (+/- 85) 21 | test integer_body_parse ... bench: 2,473 ns/iter (+/- 419) 22 | test integer_convert ... bench: 549 ns/iter (+/- 129) 23 | test string_body_parse ... bench: 2,475 ns/iter (+/- 588) 24 | test string_convert ... bench: 674 ns/iter (+/- 132) 25 | test time_body_parse ... bench: 1,505 ns/iter (+/- 222) 26 | test time_convert ... bench: 163 ns/iter (+/- 39) 27 | test uuid_body_parse ... bench: 1,464 ns/iter (+/- 227) 28 | test uuid_convert ... bench: 168 ns/iter (+/- 32) 29 | ``` 30 | 31 | #### Collection types (List, Set, Map) 32 | 33 | ``` 34 | test list_body_parse ... bench: 2,694 ns/iter (+/- 444) 35 | test list_convert ... bench: 4,953 ns/iter (+/- 720) 36 | test list_v4_body_parse ... bench: 2,578 ns/iter (+/- 933) 37 | test list_v4_convert ... bench: 4,929 ns/iter (+/- 658) 38 | test map_body_parse ... bench: 3,174 ns/iter (+/- 1,222) 39 | test map_convert ... bench: 8,087 ns/iter (+/- 1,540) 40 | test map_without_blob_body_parse ... bench: 3,787 ns/iter (+/- 772) 41 | test map_without_blob_convert ... bench: 7,867 ns/iter (+/- 1,022) 42 | ``` 43 | -------------------------------------------------------------------------------- /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-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPikalov/cdrs/7de346341a9fd72135127ebabd5240d4c3a7de7a/cdrs-logo.png -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ### v 1.2.1 2 | 3 | * Fixed buffer issue when UDT schema was changed https://github.com/AlexPikalov/cdrs/pull/191 4 | 5 | ### v 1.2.0 6 | 7 | * Fixed reconnection when a node went down and evaluation of peer address returns an error 8 | * Introduced `IntoCDRSBytes` trait and created the [auto derive](https://github.com/AlexPikalov/into-cdrs-value-derive). See [example](https://github.com/AlexPikalov/into-cdrs-value-derive/tree/master/example) on how to use derive for values needed to be converted into CDRS `Value` which could be passed into query builder as a value 9 | * Performance of node health check was improved 10 | 11 | ### v 1.1.0 12 | 13 | * Create `Blob` type. It's a wrapper for `Vec` which represents Cassandra 14 | blob type. 15 | 16 | * Fix full event to simple event mapping. 17 | 18 | * Derive `Clone` for `Bytes` 19 | -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | # Definitive guide 2 | 3 | - [Cluster configuration](./cluster-configuration.md). 4 | - [CDRS session](./cdrs-session.md): 5 | - [SSL session](./cdrs-session.md) 6 | - [Making queries](./making-query.md). 7 | - [Query values](./query-values.md) 8 | - [Cassandra-to-Rust deserialization](./deserialization.md). 9 | - [Preparing and executing queries](./preparing-and-executing-queries.md). 10 | - [Batching multiple queries](./batching-multiple-queries.md). 11 | -------------------------------------------------------------------------------- /documentation/batching-multiple-queries.md: -------------------------------------------------------------------------------- 1 | ### Batch queries 2 | 3 | _TODO: rework this section_ 4 | 5 | CDRS `Session` supports batching few queries in a single request to Apache Cassandra via implementing `cdrs::query::BatchExecutor` trait: 6 | 7 | ```rust 8 | // batch two queries 9 | use cdrs::query::{BatchQueryBuilder, QueryBatch}; 10 | 11 | let mut queries = BatchQueryBuilder::new(); 12 | queries = queries.add_query_prepared(&prepared_query); 13 | queries = queries.add_query("INSERT INTO my.store (my_int) VALUES (?)", query_values!(1 as i32)); 14 | session.batch_with_params(queries.finalyze()); 15 | 16 | // batch queries with tracing and warning information 17 | use cdrs::query::{BatchQueryBuilder, QueryBatch}; 18 | 19 | let with_tracing = true; 20 | let with_warnings = true; 21 | let mut queries = BatchQueryBuilder::new(); 22 | queries = queries.add_query_prepared(&prepared_query); 23 | queries = queries.add_query("INSERT INTO my.store (my_int) VALUES (?)", query_values!(1 as i32)); 24 | session.batch_with_params_tw(queries.finalyze(), with_tracing, with_warnings); 25 | ``` 26 | -------------------------------------------------------------------------------- /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 | ```rust 6 | use cdrs::load_balancing::RoundRobin; 7 | use cdrs::cluster::session::{new as new_session}; 8 | 9 | 10 | let load_balancing = RoundRobin::new(); 11 | let session = new_session(&cluster_config, RoundRobin::new()) 12 | .expect("session should be created"); 13 | ``` 14 | 15 | Here, 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 pool of connections for a node that was picked up in accordance to a strategy. After that CDRS gets from r2d2 pool one of available connections, and then this connection will be used for frames exchange. 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. 16 | 17 | This is how the architecture looks like: 18 | 19 |

20 | CDRS load balancing architecture 21 |

22 | 23 | ## Load balancing 24 | 25 | Any structure that implements `LoadBalancingStrategy` trait can be used in `Session` as a load balancer. 26 | 27 | CDRS provides few strategies out of the box so no additional development may not be needed: 28 | 29 | - `cdrs::load_balancing::Random` randomly picks up a node from a cluster. 30 | 31 | - `cdrs::load_balancing::RoundRobinSync` thread safe round robin balancing strategy. 32 | 33 | - `cdrs::load_balancing::RoundRobin` light weight round robin strategy that is not thread safe though. So it should be used in mono thread apps only. 34 | 35 | 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. 36 | 37 | ## Data compression 38 | 39 | CQL binary protocol allows using LZ4 and Snappy data compression in order to reduce trafic between Node and Client. 40 | 41 | CDRS provides methods for creating `Session` with different compression contexts: 42 | 43 | - `cdrs::session::new(&cluster_config, load_balancer)` creates new `Session` that will be exchanging non-compressed frames with a Cluster. 44 | 45 | - `cdrs::session::new_snappy(&cluster_config, load_balancer)` creates new `Session` that will be exchanging Snappy-compressed frames with a Cluster. 46 | 47 | - `cdrs::session::new_lz4(&cluster_config, load_balancer)` creates new `Session` that will be exchanging LZ4-compressed frames with a Cluster. 48 | 49 | Once `Session` is successfully created it can be used for communication with Cluster. 50 | 51 | ## Making queries 52 | 53 | By default `Session` structure doesn't provide an API for making queries. Query functionality becomes enabled after importing one or few of following traits: 54 | 55 | ```rust 56 | use cdrs::query::QueryExecutor; 57 | ``` 58 | 59 | ```rust 60 | use cdrs::query::PrepareExecutor; 61 | ``` 62 | 63 | ```rust 64 | use cdrs::query::ExecExecutor; 65 | ``` 66 | 67 | ```rust 68 | use cdrs::query::BatchExecutor; 69 | ``` 70 | 71 | Detailed Query API and those traits overview please find in [making query](./making-query.md) section. 72 | 73 | ### Reference 74 | 75 | 1. LZ4 compression algorithm https://en.wikipedia.org/wiki/LZ4_(compression_algorithm). 76 | 77 | 2. Snappy compression algorithm https://en.wikipedia.org/wiki/Snappy_(compression). 78 | -------------------------------------------------------------------------------- /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 multinode support in mind. In order to connect to Cassandra cluster via CDRS connection configuration should be provided: 6 | 7 | ```rust 8 | use cdrs::authenticators::NoneAuthenticator; 9 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 10 | 11 | fn main() { 12 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 13 | let cluster_config = ClusterTcpConfig(vec![node]); 14 | } 15 | ``` 16 | 17 | `ClusterTcpConfig` receives a vector of Cassandra nodes configurations. `NodeTcpConfigBuilder` is a builder that provides methods for configuring r2d2 pool of connections to a given node: 18 | 19 | ```rust 20 | let node_address = "127.0.0.1:9042"; 21 | let authenticator = NoneAuthenticator {}; 22 | let node = NodeTcpConfigBuilder::new(node_address, authenticator) 23 | .max_size(5) 24 | .min_idle(4) 25 | .max_lifetime(Some(Duration::from_secs(60))) 26 | .idle_timeout(Duration::from_secs(60)) 27 | .build(); 28 | let no_compression: CurrentSession = 29 | new_session(&cluster_config, RoundRobin::new()).expect("session should be created"); 30 | ``` 31 | 32 | All existing `NodeTcpConfigBuilder` methods have the same behaviour as ones from `r2d2::Builder`, so for more details please refer to [r2d2](https://docs.rs/r2d2/0.8.2/r2d2/struct.Builder.html) official documentation. 33 | 34 | For each node configuration, `Authenticator` should be provided. `Authenticator` 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: 35 | 36 | - `cdrs::authenticators::NoneAuthenticator` 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. 37 | 38 | - `cdrs::authenticators::PasswordAuthenticator` 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`. 39 | 40 | ```rust 41 | use cdrs::authenticators::PasswordAuthenticator; 42 | let authenticator = PasswordAuthenticator::new("user", "pass"); 43 | ``` 44 | 45 | If a node has a custom authentication strategy, corresponded `Authenticator` should be implemented by a developer and further used in `NodeTcpConfigBuilder`. 46 | 47 | To figure out how a custom `Authenticator` should be implemented refer to [src/authenticators.rs](https://github.com/AlexPikalov/cdrs/blob/master/src/authenticators.rs). 48 | 49 | ### Reference 50 | 51 | 1. Cassandra cluster configuration https://docs.datastax.com/en/cassandra/3.0/cassandra/initialize/initTOC.html. 52 | 53 | 2. ScyllaDB cluster configuration https://docs.scylladb.com/operating-scylla/ (see Cluster Management section). 54 | 55 | 3. `r2d2::Builder` documentation https://docs.rs/r2d2/0.8.2/r2d2/struct.Builder.html. 56 | -------------------------------------------------------------------------------- /documentation/deserialization.md: -------------------------------------------------------------------------------- 1 | ### Mapping results into Rust structures 2 | 3 | _TODO: provide more details and link to examples_ 4 | 5 | In ordert to query information from Cassandra DB and transform results to Rust types an structures each row in a query result should be transformed leveraging one of following traits provided by CDRS `cdrs::types::{AsRustType, AsRust, IntoRustByName, ByName, IntoRustByIndex, ByIndex}`. 6 | 7 | - `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. 8 | 9 | - `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. 10 | 11 | - `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. 12 | 13 | - `ByName` trait is the same as `IntoRustByName` but value should be neither non-set nor null. Otherwise it panics. 14 | 15 | - `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. 16 | 17 | - `ByIndex` is the same as `IntoRustByIndex` but value can be neither non-set nor null. Otherwise it panics. 18 | 19 | Relations between Cassandra and Rust types are described in [type-mapping.md](https://github.com/AlexPikalov/cdrs/blob/master/type-mapping.md). For details see examples. 20 | -------------------------------------------------------------------------------- /documentation/making-query.md: -------------------------------------------------------------------------------- 1 | # Making queries 2 | 3 | By default `Session` structure doesn't provide an API for making queries. Query functionality bacomes enabled after importing one or few of following traits: 4 | 5 | ```rust 6 | use cdrs::query::QueryExecutor; 7 | ``` 8 | 9 | This trait provides an API for making plain queries and immediately receiving responses. 10 | 11 | ```rust 12 | use cdrs::query::PrepareExecutor; 13 | ``` 14 | 15 | This trait enables query preparation on the server. After query preparation it's enough to exectute query via `cdrs::query::ExecExecutor` API prviding a query ID returned by `cdrs::query::PrepareExecutor` 16 | 17 | ```rust 18 | use cdrs::query::ExecExecutor; 19 | ``` 20 | 21 | This trait provides an API for query exectution. `PrepareExecutor` and `ExecExecutor` APIs are considered in [next sections](./preparing-and-executing-queries.md). 22 | 23 | ```rust 24 | use cdrs::query::BatchExecutor; 25 | ``` 26 | 27 | `BatchExecutor` provides functionality for executing multiple queries in a single request to Cluster. For more details refer to [Batch Queries](./batching-multiple-queries.md) section. 28 | 29 | ### `CDRSSession` trait 30 | 31 | Each of traits enumered beyond provides just a piece of full query API. They can be used independently one from another though. However if the whole query functionality is needed in a programm `use cdrs::cluster::CDRSSession` should be considered instead. 32 | 33 | `CDRSSession` source code looks following 34 | 35 | ```rust 36 | pub trait CDRSSession< 37 | 'a, 38 | T: CDRSTransport + 'static, 39 | M: r2d2::ManageConnection, Error = error::Error>, 40 | >: 41 | GetCompressor<'static> 42 | + GetConnection 43 | + QueryExecutor 44 | + PrepareExecutor 45 | + ExecExecutor 46 | + BatchExecutor 47 | { 48 | } 49 | ``` 50 | 51 | It includes all the functionality related to making queries. 52 | 53 | ### `QueryExecutor` API 54 | 55 | `QueryExecutor` trait provides various methods for immediate query execution. 56 | 57 | ```rust 58 | session.query("INSERT INTO my.numbers (my_int, my_bigint) VALUES (1, 2)").unwrap(); 59 | ``` 60 | 61 | `query` method receives a single argument which is a CQL query string. It returns `cdrs::error::Result` that in case of `SELECT` query can be mapped on corresponded Rust structure. See [CRUD example](../examples/crud_operations.rs) for details. 62 | 63 | The same query could be made leveraging something that is called Values. It allows to have generic query strings independent from actuall values. 64 | 65 | ```rust 66 | #[macro_use] 67 | extern crate cdrs; 68 | //... 69 | 70 | const insert_numbers_query: &'static str = "INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)"; 71 | let values = query_values!(1 as i32, 1 as i64); 72 | 73 | session.query_with_values(insert_numbers_query, values).unwrap(); 74 | ``` 75 | 76 | Here we've provided a generic query string for inserting numbers into `my.numbers` table. This query string doesn't have actual values hardcoded so exactly the same query can be used for multiple insert operations. Such sort of query strings can be used for Prepare-and-Execute operations when a query string is sent just once during Prepare step and then Execution operation is performed each time new values should be inserted. For more detailes see [Preparing and Executing](./preparing-and-executing-queries.md) section. 77 | 78 | However the full controll over the query can be achieved via `cdrs::query::QueryParamsBuilder`: 79 | 80 | ```rust 81 | use cdrs::query::QueryParamsBuilder; 82 | use cdrs::consistency::Consistency; 83 | 84 | let query_params = QueryParamsBuilder::new() 85 | .consistency(Consistency::Any) 86 | .finalize(); 87 | session.query_with_params("SELECT * FROM my.store", query_params).unwrap(); 88 | ``` 89 | 90 | `QueryParamsBuilder` allows to precise all possible parameters of a query: consistency, values, paging properties and others. To get all parameters please refer to CDRS API [docs](https://docs.rs/cdrs/2.0.0-beta.1/cdrs/query/struct.QueryParamsBuilder.html). 91 | 92 | Usually developers don't need to use `query_with_params` as almost all functionality is provided by such ergonomic methods as `query_with_values`, `pager` etc. 93 | 94 | ### Reference 95 | 96 | 1. `QueryParamsBuilder` API docs https://docs.rs/cdrs/2.0.0-beta.1/cdrs/query/struct.QueryParamsBuilder.html. 97 | 98 | 2. The Cassandra Query Language (CQL) http://cassandra.apache.org/doc/4.0/cql/. 99 | 100 | 3. [CDRS: Preparing and executing queries](./preparing-and-executing-queries.md) 101 | -------------------------------------------------------------------------------- /documentation/preparing-and-executing-queries.md: -------------------------------------------------------------------------------- 1 | ### Preparing queries 2 | 3 | _TODO: provide more details_ 4 | 5 | During preparing a query a server parses the query, saves parsing result into cache and returns back to a client an ID that could be further used for executing prepared statement with different parameters (such as values, consistency etc.). When a server executes prepared query it doesn't need to parse it so parsing step will be skipped. 6 | 7 | CDRS `Session` implements `cdrs::query::PrepareExecutor` trait that provides few option for preparing query: 8 | 9 | ```rust 10 | let prepared_query = session.prepare("INSERT INTO my.store (my_int, my_bigint) VALUES (?, ?)").unwrap(); 11 | 12 | // or with tracing and warnings 13 | let with_tracing = true; 14 | let with_warnings = true; 15 | 16 | let prepred_query = session.prepare_tw("INSERT INTO my.store (my_int, my_bigint) VALUES (?, ?)", with_tracing, with_warnings).unwrap(); 17 | ``` 18 | 19 | ### Executing prepared queries 20 | 21 | When query is prepared on the server client gets prepared query id of type `cdrs::query::PreparedQuery`. Having such id it's possible to execute prepared query using session methods from `cdrs::query::ExecExecutor`: 22 | 23 | ```rust 24 | // execute prepared query without specifying any extra parameters or values 25 | session.exec(&preparedQuery).unwrap(); 26 | 27 | // execute prepared query with tracing and warning information 28 | let with_tracing = true; 29 | let with_warnings = true; 30 | 31 | session.exec_tw(&preparedQuery, with_tracing, with_warnings).unwrap(); 32 | 33 | // execute prepared query with values 34 | let values_with_names = query_values!{"my_bigint" => bigint, "my_int" => int}; 35 | 36 | session.exec_with_values(&preparedQuery, values_with_names).unwrap(); 37 | 38 | // execute prepared query with values with warnings and tracing information 39 | let with_tracing = true; 40 | let with_warnings = true; 41 | 42 | let values_with_names = query_values!{"my_bigint" => 1 as i64, "my_int" => 2 as i32}; 43 | 44 | session.exec_with_values_tw(&preparedQuery, values_with_names, with_tracing, with_warnings).unwrap(); 45 | 46 | // execute prepared query with parameters 47 | use cdrs::query::QueryParamsBuilder; 48 | use cdrs::consistency::Consistency; 49 | 50 | let mut params = QueryParamsBuilder::new(); 51 | params = params.consistency(Consistency::Any); 52 | session.exec_with_parameters(&preparedQuery, params.finalize()).unwrap(); 53 | 54 | // execute prepared query with parameters, tracing and warning information 55 | use cdrs::query::QueryParamsBuilder; 56 | use cdrs::consistency::Consistency; 57 | 58 | let with_tracing = true; 59 | let with_warnings = true; 60 | let mut params = QueryParamsBuilder::new(); 61 | params = params.consistency(Consistency::Any); 62 | session.exec_with_parameters_tw(&preparedQuery, params.finalize(), with_tracing, with_warnings).unwrap(); 63 | ``` 64 | -------------------------------------------------------------------------------- /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 subsituted 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).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 `CDRSSession` methods that allow using values and query templates: 17 | 18 | - `exec_with_values` - executes previously preared 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 easy create `Value`-s CDRS provides `query_values!` macro: 27 | 28 | ```rust 29 | #[macro_use] 30 | extern crate cdrs; 31 | let values = query_values!(1 as i32, 1 as i64); 32 | ``` 33 | 34 | - `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 necesarily have the same order as a corresponded `?` in a query template: 35 | 36 | ```rust 37 | #[macro_use] 38 | extern crate cdrs; 39 | //... 40 | 41 | const insert_numbers_query: &'static str = "INSERT INTO my.numbers (my_int, my_bigint) VALUES (?, ?)"; 42 | let values = query_values!(my_bigint => 1 as i64, my_int => 1 as i64); 43 | 44 | session.query_with_values(insert_numbers_query, values).unwrap(); 45 | ``` 46 | 47 | What kind of values can be used as `query_values!` arguments? All types that have implementations of [`Into` trait](https://docs.rs/cdrs/2.0.0-beta.1/cdrs/types/value/struct.Bytes.html). 48 | 49 | 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 recurcive implementation. See [CRUD example](../examples/crud_operations.rs). 50 | 51 | ### Reference 52 | 53 | 1. Cassandra official docs - User Defined Types http://cassandra.apache.org/doc/4.0/cql/types.html#grammar-token-user_defined_type. 54 | 55 | 2. Datastax - User Defined Types https://docs.datastax.com/en/cql/3.3/cql/cql_using/useCreateUDT.html. 56 | 57 | 3. ScyllaDB - User Defined Types https://docs.scylladb.com/getting-started/types/ 58 | 59 | 4. [CDRS CRUD Example](../examples/crud_operations.rs) 60 | 61 | 5. https://github.com/AlexPikalov/cdrs-helpers-derive 62 | -------------------------------------------------------------------------------- /documentation/schemes/cdrs-load-balancing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPikalov/cdrs/7de346341a9fd72135127ebabd5240d4c3a7de7a/documentation/schemes/cdrs-load-balancing.png -------------------------------------------------------------------------------- /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 | - [`server_events.rs`](./server_events.rs) illustrates a process of server events (create table, schema change etc.) listening. 9 | - [`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. 10 | -------------------------------------------------------------------------------- /examples/crud_operations.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | #[macro_use] 6 | extern crate maplit; 7 | 8 | use std::collections::HashMap; 9 | 10 | use cdrs::authenticators::StaticPasswordAuthenticator; 11 | use cdrs::cluster::session::{new as new_session, Session}; 12 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 13 | use cdrs::load_balancing::RoundRobin; 14 | use cdrs::query::*; 15 | 16 | use cdrs::frame::IntoBytes; 17 | use cdrs::types::from_cdrs::FromCDRSByName; 18 | use cdrs::types::prelude::*; 19 | 20 | type CurrentSession = Session>>; 21 | 22 | fn main() { 23 | let user = "user"; 24 | let password = "password"; 25 | let auth = StaticPasswordAuthenticator::new(&user, &password); 26 | let node = NodeTcpConfigBuilder::new("localhost:9042", auth).build(); 27 | let cluster_config = ClusterTcpConfig(vec![node]); 28 | let no_compression: CurrentSession = 29 | new_session(&cluster_config, RoundRobin::new()).expect("session should be created"); 30 | 31 | create_keyspace(&no_compression); 32 | create_udt(&no_compression); 33 | create_table(&no_compression); 34 | insert_struct(&no_compression); 35 | select_struct(&no_compression); 36 | update_struct(&no_compression); 37 | delete_struct(&no_compression); 38 | } 39 | 40 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 41 | struct RowStruct { 42 | key: i32, 43 | user: User, 44 | map: HashMap, 45 | list: Vec, 46 | } 47 | 48 | impl RowStruct { 49 | fn into_query_values(self) -> QueryValues { 50 | query_values!("key" => self.key, "user" => self.user, "map" => self.map, "list" => self.list) 51 | } 52 | } 53 | 54 | #[derive(Debug, Clone, PartialEq, IntoCDRSValue, TryFromUDT)] 55 | struct User { 56 | username: String, 57 | } 58 | 59 | fn create_keyspace(session: &CurrentSession) { 60 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 61 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 62 | session.query(create_ks).expect("Keyspace creation error"); 63 | } 64 | 65 | fn create_udt(session: &CurrentSession) { 66 | let create_type_cql = "CREATE TYPE IF NOT EXISTS test_ks.user (username text)"; 67 | session 68 | .query(create_type_cql) 69 | .expect("Keyspace creation error"); 70 | } 71 | 72 | fn create_table(session: &CurrentSession) { 73 | let create_table_cql = 74 | "CREATE TABLE IF NOT EXISTS test_ks.my_test_table (key int PRIMARY KEY, \ 75 | user frozen, map map>, list list>);"; 76 | session 77 | .query(create_table_cql) 78 | .expect("Table creation error"); 79 | } 80 | 81 | fn insert_struct(session: &CurrentSession) { 82 | let row = RowStruct { 83 | key: 3i32, 84 | user: User { 85 | username: "John".to_string(), 86 | }, 87 | map: hashmap! { "John".to_string() => User { username: "John".to_string() } }, 88 | list: vec![User { 89 | username: "John".to_string(), 90 | }], 91 | }; 92 | 93 | let insert_struct_cql = "INSERT INTO test_ks.my_test_table \ 94 | (key, user, map, list) VALUES (?, ?, ?, ?)"; 95 | session 96 | .query_with_values(insert_struct_cql, row.into_query_values()) 97 | .expect("insert"); 98 | } 99 | 100 | fn select_struct(session: &CurrentSession) { 101 | let select_struct_cql = "SELECT * FROM test_ks.my_test_table"; 102 | let rows = session 103 | .query(select_struct_cql) 104 | .expect("query") 105 | .get_body() 106 | .expect("get body") 107 | .into_rows() 108 | .expect("into rows"); 109 | 110 | for row in rows { 111 | let my_row: RowStruct = RowStruct::try_from_row(row).expect("into RowStruct"); 112 | println!("struct got: {:?}", my_row); 113 | } 114 | } 115 | 116 | fn update_struct(session: &CurrentSession) { 117 | let update_struct_cql = "UPDATE test_ks.my_test_table SET user = ? WHERE key = ?"; 118 | let upd_user = User { 119 | username: "Marry".to_string(), 120 | }; 121 | let user_key = 1i32; 122 | session 123 | .query_with_values(update_struct_cql, query_values!(upd_user, user_key)) 124 | .expect("update"); 125 | } 126 | 127 | fn delete_struct(session: &CurrentSession) { 128 | let delete_struct_cql = "DELETE FROM test_ks.my_test_table WHERE key = ?"; 129 | let user_key = 1i32; 130 | session 131 | .query_with_values(delete_struct_cql, query_values!(user_key)) 132 | .expect("delete"); 133 | } 134 | -------------------------------------------------------------------------------- /examples/dynamic_cluster.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | #[macro_use] 6 | extern crate maplit; 7 | 8 | use std::collections::HashMap; 9 | use std::io; 10 | use std::process::{Command, Output}; 11 | 12 | use cdrs::authenticators::NoneAuthenticator; 13 | use cdrs::cluster::session::{new_dynamic as new_session, Session}; 14 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 15 | use cdrs::load_balancing::RoundRobin; 16 | use cdrs::query::*; 17 | 18 | use cdrs::frame::IntoBytes; 19 | use cdrs::types::from_cdrs::FromCDRSByName; 20 | use cdrs::types::prelude::*; 21 | 22 | type CurrentSession = Session>>; 23 | 24 | fn start_node_a(_: A) -> io::Result { 25 | Command::new("docker") 26 | .args(&[ 27 | "run", 28 | "-d", 29 | "-p", 30 | "9042:9042", 31 | "--name", 32 | "cass1", 33 | "cassandra:3.9", 34 | ]) 35 | .output() 36 | } 37 | 38 | fn start_node_b(_: B) -> io::Result { 39 | Command::new("docker") 40 | .args(&[ 41 | "run", 42 | "-d", 43 | "-p", 44 | "9043:9042", 45 | "--name", 46 | "cass2", 47 | "-e", 48 | "CASSANDRA_SEEDS=\"$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' cass1)\"", 49 | "cassandra:3.9", 50 | ]) 51 | .output() 52 | } 53 | 54 | fn remove_container_a(_: A) -> io::Result { 55 | Command::new("docker") 56 | .args(&["stop", "cass1"]) 57 | .output() 58 | .and_then(|_| Command::new("docker").args(&["rm", "cass1"]).output()) 59 | } 60 | 61 | fn remove_container_b(_: B) -> io::Result { 62 | Command::new("docker") 63 | .args(&["stop", "cass2"]) 64 | .output() 65 | .and_then(|_| Command::new("docker").args(&["rm", "cass2"]).output()) 66 | } 67 | 68 | fn start_cluster() { 69 | println!("> > Starting node a..."); 70 | remove_container_a(()) 71 | .and_then(start_node_a) 72 | .expect("starting first node"); 73 | 74 | ::std::thread::sleep_ms(15_000); 75 | 76 | println!("> > Starting node b..."); 77 | remove_container_b(()) 78 | .and_then(start_node_b) 79 | .expect("starting second node"); 80 | 81 | ::std::thread::sleep_ms(15_000); 82 | } 83 | 84 | fn main() { 85 | let auth = NoneAuthenticator {}; 86 | let node_a = NodeTcpConfigBuilder::new("127.0.0.1:9042", auth.clone()).build(); 87 | let node_b = NodeTcpConfigBuilder::new("127.0.0.1:9043", auth.clone()).build(); 88 | let event_src = NodeTcpConfigBuilder::new("127.0.0.1:9042", auth.clone()).build(); 89 | let cluster_config = ClusterTcpConfig(vec![node_a, node_b]); 90 | 91 | // println!("> Starting cluster..."); 92 | // start_cluster(); 93 | 94 | let no_compression: CurrentSession = new_session(&cluster_config, RoundRobin::new(), event_src) 95 | .expect("session should be created"); 96 | 97 | create_keyspace(&no_compression); 98 | create_udt(&no_compression); 99 | create_table(&no_compression); 100 | 101 | println!("> Stopping node b..."); 102 | remove_container_b(()); 103 | println!("> waiting 30 secs..."); 104 | ::std::thread::sleep_ms(30_000); 105 | println!("> stopped"); 106 | 107 | insert_struct(&no_compression); 108 | select_struct(&no_compression); 109 | update_struct(&no_compression); 110 | delete_struct(&no_compression); 111 | } 112 | 113 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 114 | struct RowStruct { 115 | key: i32, 116 | user: User, 117 | map: HashMap, 118 | list: Vec, 119 | } 120 | 121 | impl RowStruct { 122 | fn into_query_values(self) -> QueryValues { 123 | query_values!("key" => self.key, "user" => self.user, "map" => self.map, "list" => self.list) 124 | } 125 | } 126 | 127 | #[derive(Debug, Clone, PartialEq, IntoCDRSValue, TryFromUDT)] 128 | struct User { 129 | username: String, 130 | } 131 | 132 | fn create_keyspace(session: &CurrentSession) { 133 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 134 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 135 | session.query(create_ks).expect("Keyspace creation error"); 136 | } 137 | 138 | fn create_udt(session: &CurrentSession) { 139 | let create_type_cql = "CREATE TYPE IF NOT EXISTS test_ks.user (username text)"; 140 | session 141 | .query(create_type_cql) 142 | .expect("Keyspace creation error"); 143 | } 144 | 145 | fn create_table(session: &CurrentSession) { 146 | let create_table_cql = 147 | "CREATE TABLE IF NOT EXISTS test_ks.my_test_table (key int PRIMARY KEY, \ 148 | user frozen, map map>, list list>);"; 149 | session 150 | .query(create_table_cql) 151 | .expect("Table creation error"); 152 | } 153 | 154 | fn insert_struct(session: &CurrentSession) { 155 | let row = RowStruct { 156 | key: 3i32, 157 | user: User { 158 | username: "John".to_string(), 159 | }, 160 | map: hashmap! { "John".to_string() => User { username: "John".to_string() } }, 161 | list: vec![User { 162 | username: "John".to_string(), 163 | }], 164 | }; 165 | 166 | let insert_struct_cql = "INSERT INTO test_ks.my_test_table \ 167 | (key, user, map, list) VALUES (?, ?, ?, ?)"; 168 | session 169 | .query_with_values(insert_struct_cql, row.into_query_values()) 170 | .expect("insert"); 171 | } 172 | 173 | fn select_struct(session: &CurrentSession) { 174 | let select_struct_cql = "SELECT * FROM test_ks.my_test_table"; 175 | let rows = session 176 | .query(select_struct_cql) 177 | .expect("query") 178 | .get_body() 179 | .expect("get body") 180 | .into_rows() 181 | .expect("into rows"); 182 | 183 | for row in rows { 184 | let my_row: RowStruct = RowStruct::try_from_row(row).expect("into RowStruct"); 185 | println!("struct got: {:?}", my_row); 186 | } 187 | } 188 | 189 | fn update_struct(session: &CurrentSession) { 190 | let update_struct_cql = "UPDATE test_ks.my_test_table SET user = ? WHERE key = ?"; 191 | let upd_user = User { 192 | username: "Marry".to_string(), 193 | }; 194 | let user_key = 1i32; 195 | session 196 | .query_with_values(update_struct_cql, query_values!(upd_user, user_key)) 197 | .expect("update"); 198 | } 199 | 200 | fn delete_struct(session: &CurrentSession) { 201 | let delete_struct_cql = "DELETE FROM test_ks.my_test_table WHERE key = ?"; 202 | let user_key = 1i32; 203 | session 204 | .query_with_values(delete_struct_cql, query_values!(user_key)) 205 | .expect("delete"); 206 | } 207 | -------------------------------------------------------------------------------- /examples/insert_collection.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | #[macro_use] 6 | extern crate maplit; 7 | 8 | use std::collections::HashMap; 9 | 10 | use cdrs::authenticators::StaticPasswordAuthenticator; 11 | use cdrs::cluster::session::{new as new_session, Session}; 12 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 13 | use cdrs::load_balancing::RoundRobin; 14 | use cdrs::query::*; 15 | 16 | use cdrs::frame::IntoBytes; 17 | use cdrs::types::from_cdrs::FromCDRSByName; 18 | use cdrs::types::prelude::*; 19 | 20 | type CurrentSession = Session>>; 21 | 22 | fn main() { 23 | let user = "user"; 24 | let password = "password"; 25 | let auth = StaticPasswordAuthenticator::new(&user, &password); 26 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", auth).build(); 27 | let cluster_config = ClusterTcpConfig(vec![node]); 28 | let no_compression: CurrentSession = 29 | new_session(&cluster_config, RoundRobin::new()).expect("session should be created"); 30 | 31 | create_keyspace(&no_compression); 32 | create_udt(&no_compression); 33 | create_table(&no_compression); 34 | 35 | insert_struct(&no_compression); 36 | append_list(&no_compression); 37 | prepend_list(&no_compression); 38 | append_set(&no_compression); 39 | append_map(&no_compression); 40 | 41 | select_struct(&no_compression); 42 | delete_struct(&no_compression); 43 | } 44 | 45 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 46 | struct RowStruct { 47 | key: i32, 48 | map: HashMap, 49 | list: Vec, 50 | cset: Vec, 51 | } 52 | 53 | impl RowStruct { 54 | fn into_query_values(self) -> QueryValues { 55 | query_values!("key" => self.key, "map" => self.map, "list" => self.list, "cset" => self.cset) 56 | } 57 | } 58 | 59 | #[derive(Debug, Clone, PartialEq, IntoCDRSValue, TryFromUDT)] 60 | struct User { 61 | username: String, 62 | } 63 | 64 | fn create_keyspace(session: &CurrentSession) { 65 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 66 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 67 | session.query(create_ks).expect("Keyspace creation error"); 68 | } 69 | 70 | fn create_udt(session: &CurrentSession) { 71 | let create_type_cql = "CREATE TYPE IF NOT EXISTS test_ks.user (username text)"; 72 | session 73 | .query(create_type_cql) 74 | .expect("Keyspace creation error"); 75 | } 76 | 77 | fn create_table(session: &CurrentSession) { 78 | let create_table_cql = 79 | "CREATE TABLE IF NOT EXISTS test_ks.collection_table (key int PRIMARY KEY, \ 80 | user frozen, map map>, \ 81 | list list>, cset set>);"; 82 | session 83 | .query(create_table_cql) 84 | .expect("Table creation error"); 85 | } 86 | 87 | fn append_list(session: &CurrentSession) { 88 | let key = 3i32; 89 | let extra_values = vec![ 90 | User { 91 | username: "William".to_string(), 92 | }, 93 | User { 94 | username: "Averel".to_string(), 95 | }, 96 | ]; 97 | let append_list_cql = "UPDATE test_ks.collection_table SET list = list + ? \ 98 | WHERE key = ?"; 99 | session 100 | .query_with_values(append_list_cql, query_values!(extra_values, key)) 101 | .expect("append list"); 102 | } 103 | 104 | fn prepend_list(session: &CurrentSession) { 105 | let key = 3i32; 106 | let extra_values = vec![ 107 | User { 108 | username: "Joe".to_string(), 109 | }, 110 | User { 111 | username: "Jack".to_string(), 112 | }, 113 | ]; 114 | let prepend_list_cql = "UPDATE test_ks.collection_table SET list = ? + list \ 115 | WHERE key = ?"; 116 | session 117 | .query_with_values(prepend_list_cql, query_values!(extra_values, key)) 118 | .expect("prepend list"); 119 | } 120 | 121 | fn append_set(session: &CurrentSession) { 122 | let key = 3i32; 123 | let extra_values = vec![ 124 | User { 125 | username: "William".to_string(), 126 | }, 127 | User { 128 | username: "Averel".to_string(), 129 | }, 130 | ]; 131 | let append_set_cql = "UPDATE test_ks.collection_table SET cset = cset + ? \ 132 | WHERE key = ?"; 133 | session 134 | .query_with_values(append_set_cql, query_values!(extra_values, key)) 135 | .expect("append set"); 136 | } 137 | 138 | fn append_map(session: &CurrentSession) { 139 | let key = 3i32; 140 | let extra_values = hashmap![ 141 | "Joe".to_string() => User { username: "Joe".to_string() }, 142 | "Jack".to_string() => User { username: "Jack".to_string() }, 143 | ]; 144 | let append_map_cql = "UPDATE test_ks.collection_table SET map = map + ? \ 145 | WHERE key = ?"; 146 | session 147 | .query_with_values(append_map_cql, query_values!(extra_values, key)) 148 | .expect("append map"); 149 | } 150 | 151 | fn insert_struct(session: &CurrentSession) { 152 | let row = RowStruct { 153 | key: 3i32, 154 | map: hashmap! { "John".to_string() => User { username: "John".to_string() } }, 155 | list: vec![User { 156 | username: "John".to_string(), 157 | }], 158 | cset: vec![User { 159 | username: "John".to_string(), 160 | }], 161 | }; 162 | 163 | let insert_struct_cql = "INSERT INTO test_ks.collection_table \ 164 | (key, map, list, cset) VALUES (?, ?, ?, ?)"; 165 | session 166 | .query_with_values(insert_struct_cql, row.into_query_values()) 167 | .expect("insert"); 168 | } 169 | 170 | fn select_struct(session: &CurrentSession) { 171 | let select_struct_cql = "SELECT * FROM test_ks.collection_table"; 172 | let rows = session 173 | .query(select_struct_cql) 174 | .expect("query") 175 | .get_body() 176 | .expect("get body") 177 | .into_rows() 178 | .expect("into rows"); 179 | 180 | for row in rows { 181 | let my_row: RowStruct = RowStruct::try_from_row(row).expect("into RowStruct"); 182 | println!("struct got: {:#?}", my_row); 183 | } 184 | } 185 | 186 | fn delete_struct(session: &CurrentSession) { 187 | let delete_struct_cql = "DELETE FROM test_ks.collection_table WHERE key = ?"; 188 | let user_key = 3i32; 189 | session 190 | .query_with_values(delete_struct_cql, query_values!(user_key)) 191 | .expect("delete"); 192 | } 193 | -------------------------------------------------------------------------------- /examples/multiple_thread.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | 6 | use std::sync::Arc; 7 | use std::thread; 8 | 9 | use cdrs::authenticators::NoneAuthenticator; 10 | use cdrs::cluster::session::{new as new_session, Session}; 11 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 12 | use cdrs::load_balancing::RoundRobinSync; 13 | use cdrs::query::*; 14 | 15 | use cdrs::frame::IntoBytes; 16 | use cdrs::types::from_cdrs::FromCDRSByName; 17 | use cdrs::types::prelude::*; 18 | 19 | type CurrentSession = Session>>; 20 | 21 | fn main() { 22 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 23 | let cluster_config = ClusterTcpConfig(vec![node]); 24 | let lb = RoundRobinSync::new(); 25 | let no_compression: Arc = 26 | Arc::new(new_session(&cluster_config, lb).expect("session should be created")); 27 | 28 | create_keyspace(&no_compression.clone()); 29 | create_table(&no_compression.clone()); 30 | 31 | for i in 0..20 { 32 | let thread_session = no_compression.clone(); 33 | thread::spawn(move || { 34 | insert_struct(&thread_session, i); 35 | }) 36 | .join() 37 | .expect("thread error"); 38 | } 39 | 40 | select_struct(&no_compression); 41 | } 42 | 43 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 44 | struct RowStruct { 45 | key: i32, 46 | } 47 | 48 | impl RowStruct { 49 | fn into_query_values(self) -> QueryValues { 50 | query_values!("key" => self.key) 51 | } 52 | } 53 | 54 | #[derive(Debug, Clone, PartialEq, IntoCDRSValue, TryFromUDT)] 55 | struct User { 56 | username: String, 57 | } 58 | 59 | fn create_keyspace(session: &CurrentSession) { 60 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 61 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 62 | session.query(create_ks).expect("Keyspace creation error"); 63 | } 64 | 65 | fn create_table(session: &CurrentSession) { 66 | let create_table_cql = 67 | "CREATE TABLE IF NOT EXISTS test_ks.multi_thread_table (key int PRIMARY KEY);"; 68 | session 69 | .query(create_table_cql) 70 | .expect("Table creation error"); 71 | } 72 | 73 | fn insert_struct(session: &CurrentSession, key: i32) { 74 | let row = RowStruct { key }; 75 | 76 | let insert_struct_cql = "INSERT INTO test_ks.multi_thread_table (key) VALUES (?)"; 77 | session 78 | .query_with_values(insert_struct_cql, row.into_query_values()) 79 | .expect("insert"); 80 | } 81 | 82 | fn select_struct(session: &CurrentSession) { 83 | let select_struct_cql = "SELECT * FROM test_ks.multi_thread_table"; 84 | let rows = session 85 | .query(select_struct_cql) 86 | .expect("query") 87 | .get_body() 88 | .expect("get body") 89 | .into_rows() 90 | .expect("into rows"); 91 | 92 | for row in rows { 93 | let my_row: RowStruct = RowStruct::try_from_row(row).expect("into RowStruct"); 94 | println!("struct got: {:?}", my_row); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/paged_query.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | 6 | use cdrs::authenticators::NoneAuthenticator; 7 | use cdrs::cluster::session::{new as new_session, Session}; 8 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, PagerState, TcpConnectionPool}; 9 | use cdrs::load_balancing::RoundRobin; 10 | use cdrs::query::*; 11 | 12 | use cdrs::frame::IntoBytes; 13 | use cdrs::types::from_cdrs::FromCDRSByName; 14 | use cdrs::types::prelude::*; 15 | 16 | type CurrentSession = Session>>; 17 | 18 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 19 | struct RowStruct { 20 | key: i32, 21 | } 22 | 23 | impl RowStruct { 24 | fn into_query_values(self) -> QueryValues { 25 | query_values!("key" => self.key) 26 | } 27 | } 28 | 29 | fn main() { 30 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 31 | let cluster_config = ClusterTcpConfig(vec![node]); 32 | let lb = RoundRobin::new(); 33 | let no_compression = new_session(&cluster_config, lb).expect("session should be created"); 34 | 35 | create_keyspace(&no_compression); 36 | create_table(&no_compression); 37 | fill_table(&no_compression); 38 | println!("Internal pager state\n"); 39 | paged_selection_query(&no_compression); 40 | println!("\n\nExternal pager state for stateless executions\n"); 41 | paged_selection_query_with_state(&no_compression, PagerState::new()); 42 | } 43 | 44 | fn create_keyspace(session: &CurrentSession) { 45 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 46 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 47 | session.query(create_ks).expect("Keyspace creation error"); 48 | } 49 | 50 | fn create_table(session: &CurrentSession) { 51 | let create_table_cql = 52 | "CREATE TABLE IF NOT EXISTS test_ks.my_test_table (key int PRIMARY KEY, \ 53 | user test_ks.user, map map>, list list>);"; 54 | session 55 | .query(create_table_cql) 56 | .expect("Table creation error"); 57 | } 58 | 59 | fn fill_table(session: &CurrentSession) { 60 | let insert_struct_cql = "INSERT INTO test_ks.my_test_table (key) VALUES (?)"; 61 | 62 | for k in 100..110 { 63 | let row = RowStruct { key: k as i32 }; 64 | 65 | session 66 | .query_with_values(insert_struct_cql, row.into_query_values()) 67 | .expect("insert"); 68 | } 69 | } 70 | 71 | fn paged_selection_query(session: &CurrentSession) { 72 | let q = "SELECT * FROM test_ks.my_test_table;"; 73 | let mut pager = session.paged(2); 74 | let mut query_pager = pager.query(q); 75 | 76 | loop { 77 | let rows = query_pager.next().expect("pager next"); 78 | for row in rows { 79 | let my_row = RowStruct::try_from_row(row).expect("decode row"); 80 | println!("row - {:?}", my_row); 81 | } 82 | 83 | if !query_pager.has_more() { 84 | break; 85 | } 86 | } 87 | } 88 | 89 | fn paged_selection_query_with_state(session: &CurrentSession, state: PagerState) { 90 | let mut st = state; 91 | 92 | loop { 93 | let q = "SELECT * FROM test_ks.my_test_table;"; 94 | let mut pager = session.paged(2); 95 | let mut query_pager = pager.query_with_pager_state(q, st); 96 | 97 | let rows = query_pager.next().expect("pager next"); 98 | for row in rows { 99 | let my_row = RowStruct::try_from_row(row).expect("decode row"); 100 | println!("row - {:?}", my_row); 101 | } 102 | 103 | if !query_pager.has_more() { 104 | break; 105 | } 106 | 107 | st = query_pager.pager_state(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/prepare_batch_execute.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cdrs; 3 | #[macro_use] 4 | extern crate cdrs_helpers_derive; 5 | 6 | use cdrs::authenticators::NoneAuthenticator; 7 | use cdrs::cluster::session::{new as new_session, Session}; 8 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 9 | use cdrs::load_balancing::RoundRobin; 10 | use cdrs::query::*; 11 | 12 | use cdrs::frame::IntoBytes; 13 | use cdrs::types::from_cdrs::FromCDRSByName; 14 | use cdrs::types::prelude::*; 15 | 16 | type CurrentSession = Session>>; 17 | 18 | #[derive(Clone, Debug, IntoCDRSValue, TryFromRow, PartialEq)] 19 | struct RowStruct { 20 | key: i32, 21 | } 22 | 23 | impl RowStruct { 24 | fn into_query_values(self) -> QueryValues { 25 | // **IMPORTANT NOTE:** query values should be WITHOUT NAMES 26 | // https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec#L413 27 | query_values!(self.key) 28 | } 29 | } 30 | 31 | fn main() { 32 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 33 | let cluster_config = ClusterTcpConfig(vec![node]); 34 | let lb = RoundRobin::new(); 35 | let no_compression = new_session(&cluster_config, lb).expect("session should be created"); 36 | 37 | create_keyspace(&no_compression); 38 | create_table(&no_compression); 39 | 40 | let insert_struct_cql = "INSERT INTO test_ks.my_test_table (key) VALUES (?)"; 41 | let prepared_query = no_compression 42 | .prepare(insert_struct_cql) 43 | .expect("Prepare query error"); 44 | 45 | for k in 100..110 { 46 | let row = RowStruct { key: k as i32 }; 47 | 48 | insert_row(&no_compression, row, &prepared_query); 49 | } 50 | 51 | batch_few_queries(&no_compression, &insert_struct_cql); 52 | } 53 | 54 | fn create_keyspace(session: &CurrentSession) { 55 | let create_ks: &'static str = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \ 56 | 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"; 57 | session.query(create_ks).expect("Keyspace creation error"); 58 | } 59 | 60 | fn create_table(session: &CurrentSession) { 61 | let create_table_cql = 62 | "CREATE TABLE IF NOT EXISTS test_ks.my_test_table (key int PRIMARY KEY);"; 63 | session 64 | .query(create_table_cql) 65 | .expect("Table creation error"); 66 | } 67 | 68 | fn insert_row(session: &CurrentSession, row: RowStruct, prepared_query: &PreparedQuery) { 69 | session 70 | .exec_with_values(prepared_query, row.into_query_values()) 71 | .expect("exec_with_values error"); 72 | } 73 | 74 | fn batch_few_queries(session: &CurrentSession, query: &str) { 75 | let prepared_query = session.prepare(query).expect("Prepare query error"); 76 | let row_1 = RowStruct { key: 1001 as i32 }; 77 | let row_2 = RowStruct { key: 2001 as i32 }; 78 | 79 | let batch = BatchQueryBuilder::new() 80 | .add_query_prepared(prepared_query, row_1.into_query_values()) 81 | .add_query(query, row_2.into_query_values()) 82 | .finalize() 83 | .expect("batch builder"); 84 | 85 | session.batch_with_params(batch).expect("batch query error"); 86 | } 87 | -------------------------------------------------------------------------------- /examples/server_events.rs: -------------------------------------------------------------------------------- 1 | extern crate cdrs; 2 | 3 | use std::iter::Iterator; 4 | use std::thread; 5 | 6 | use cdrs::authenticators::NoneAuthenticator; 7 | use cdrs::cluster::session::new as new_session; 8 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder}; 9 | use cdrs::compression::Compression; 10 | use cdrs::frame::events::{ChangeType, ServerEvent, SimpleServerEvent, Target}; 11 | use cdrs::load_balancing::RoundRobin; 12 | 13 | const _ADDR: &'static str = "127.0.0.1:9042"; 14 | 15 | fn main() { 16 | let node = NodeTcpConfigBuilder::new("127.0.0.1:9042", NoneAuthenticator {}).build(); 17 | let cluster_config = ClusterTcpConfig(vec![node]); 18 | let lb = RoundRobin::new(); 19 | let no_compression = new_session(&cluster_config, lb).expect("session should be created"); 20 | 21 | let (listener, stream) = no_compression 22 | .listen( 23 | "127.0.0.1:9042", 24 | NoneAuthenticator {}, 25 | vec![SimpleServerEvent::SchemaChange], 26 | ) 27 | .expect("listen error"); 28 | 29 | thread::spawn(move || listener.start(&Compression::None).unwrap()); 30 | 31 | let new_tables = stream 32 | // inspects all events in a stream 33 | .inspect(|event| println!("inspect event {:?}", event)) 34 | // filter by event's type: schema changes 35 | .filter(|event| event == &SimpleServerEvent::SchemaChange) 36 | // filter by event's specific information: new table was added 37 | .filter(|event| match event { 38 | &ServerEvent::SchemaChange(ref event) => { 39 | event.change_type == ChangeType::Created && event.target == Target::Table 40 | } 41 | _ => false, 42 | }); 43 | 44 | println!("Start listen for server events"); 45 | 46 | for change in new_tables { 47 | println!("server event {:?}", change); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fixtures/frame/events/schema-change-created-keyspace: -------------------------------------------------------------------------------- 1 | SCHEMA_CHANGECREATEDKEYSPACEmy_ks -------------------------------------------------------------------------------- /fixtures/frame/events/status-change-down: -------------------------------------------------------------------------------- 1 | STATUS_CHANGEDOWN -------------------------------------------------------------------------------- /fixtures/frame/events/status-change-up: -------------------------------------------------------------------------------- 1 | STATUS_CHANGEUP -------------------------------------------------------------------------------- /fixtures/frame/events/topology-change-new-node: -------------------------------------------------------------------------------- 1 | TOPOLOGY_CHANGENEW_NODE -------------------------------------------------------------------------------- /fixtures/frame/events/topology-change-removed-node: -------------------------------------------------------------------------------- 1 | TOPOLOGY_CHANGE REMOVED_NODE -------------------------------------------------------------------------------- /src/authenticators.rs: -------------------------------------------------------------------------------- 1 | use crate::types::CBytes; 2 | 3 | pub trait Authenticator: Clone + Send + Sync { 4 | fn get_auth_token(&self) -> CBytes; 5 | fn get_cassandra_name(&self) -> Option<&str>; 6 | } 7 | 8 | #[derive(Debug, Clone)] 9 | #[deprecated( 10 | since = "2.1.0", 11 | note = "`PasswordAuthenticator` is deprecated in favour of `StaticPasswordAuthenticator` because the second one doesn't require static lifetime for credentials thus it's easier to use." 12 | )] 13 | pub struct PasswordAuthenticator<'a> { 14 | username: &'a str, 15 | password: &'a str, 16 | } 17 | 18 | #[allow(deprecated)] 19 | impl<'a> PasswordAuthenticator<'a> { 20 | pub fn new<'b>(username: &'b str, password: &'b str) -> PasswordAuthenticator<'b> { 21 | PasswordAuthenticator { 22 | username: username, 23 | password: password, 24 | } 25 | } 26 | } 27 | 28 | #[allow(deprecated)] 29 | impl<'a> Authenticator for PasswordAuthenticator<'a> { 30 | fn get_auth_token(&self) -> CBytes { 31 | let mut token = vec![0]; 32 | token.extend_from_slice(self.username.as_bytes()); 33 | token.push(0); 34 | token.extend_from_slice(self.password.as_bytes()); 35 | 36 | CBytes::new(token) 37 | } 38 | 39 | fn get_cassandra_name(&self) -> Option<&str> { 40 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone)] 45 | pub struct StaticPasswordAuthenticator { 46 | username: String, 47 | password: String, 48 | } 49 | 50 | impl StaticPasswordAuthenticator { 51 | pub fn new(username: S, password: S) -> StaticPasswordAuthenticator { 52 | StaticPasswordAuthenticator { 53 | username: username.to_string(), 54 | password: password.to_string(), 55 | } 56 | } 57 | } 58 | 59 | impl Authenticator for StaticPasswordAuthenticator { 60 | fn get_auth_token(&self) -> CBytes { 61 | let mut token = vec![0]; 62 | token.extend_from_slice(self.username.as_bytes()); 63 | token.push(0); 64 | token.extend_from_slice(self.password.as_bytes()); 65 | 66 | CBytes::new(token) 67 | } 68 | 69 | fn get_cassandra_name(&self) -> Option<&str> { 70 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone)] 75 | pub struct NoneAuthenticator; 76 | 77 | impl Authenticator for NoneAuthenticator { 78 | fn get_auth_token(&self) -> CBytes { 79 | CBytes::new(vec![0]) 80 | } 81 | 82 | fn get_cassandra_name(&self) -> Option<&str> { 83 | None 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | #[allow(deprecated)] 89 | mod tests { 90 | use super::*; 91 | 92 | #[test] 93 | fn test_password_authenticator_trait_impl() { 94 | let authenticator = PasswordAuthenticator::new("a", "a"); 95 | let _ = authenticator_tester(Box::new(authenticator)); 96 | } 97 | 98 | #[test] 99 | fn test_password_authenticator_new() { 100 | PasswordAuthenticator::new("foo", "bar"); 101 | } 102 | 103 | #[test] 104 | fn test_password_authenticator_get_cassandra_name() { 105 | let auth = PasswordAuthenticator::new("foo", "bar"); 106 | assert_eq!( 107 | auth.get_cassandra_name(), 108 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 109 | ); 110 | } 111 | 112 | #[test] 113 | fn test_password_authenticator_get_auth_token() { 114 | let auth = PasswordAuthenticator::new("foo", "bar"); 115 | let mut expected_token = vec![0]; 116 | expected_token.extend_from_slice("foo".as_bytes()); 117 | expected_token.push(0); 118 | expected_token.extend_from_slice("bar".as_bytes()); 119 | 120 | assert_eq!(auth.get_auth_token().into_plain().unwrap(), expected_token); 121 | } 122 | 123 | #[test] 124 | fn test_static_password_authenticator_trait_impl() { 125 | let authenticator = StaticPasswordAuthenticator::new("a", "a"); 126 | let _ = authenticator_tester(Box::new(authenticator)); 127 | } 128 | 129 | #[test] 130 | fn test_static_password_authenticator_new() { 131 | StaticPasswordAuthenticator::new("foo", "bar"); 132 | } 133 | 134 | #[test] 135 | fn test_static_password_authenticator_get_cassandra_name() { 136 | let auth = StaticPasswordAuthenticator::new("foo", "bar"); 137 | assert_eq!( 138 | auth.get_cassandra_name(), 139 | Some("org.apache.cassandra.auth.PasswordAuthenticator") 140 | ); 141 | } 142 | 143 | #[test] 144 | fn test_static_password_authenticator_get_auth_token() { 145 | let auth = PasswordAuthenticator::new("foo", "bar"); 146 | let mut expected_token = vec![0]; 147 | expected_token.extend_from_slice("foo".as_bytes()); 148 | expected_token.push(0); 149 | expected_token.extend_from_slice("bar".as_bytes()); 150 | 151 | assert_eq!(auth.get_auth_token().into_plain().unwrap(), expected_token); 152 | } 153 | 154 | #[test] 155 | fn test_authenticator_none_get_cassandra_name() { 156 | let auth = NoneAuthenticator; 157 | assert_eq!(auth.get_cassandra_name(), None); 158 | assert_eq!(auth.get_auth_token().into_plain().unwrap(), vec![0]); 159 | } 160 | 161 | fn authenticator_tester(_authenticator: Box) {} 162 | } 163 | -------------------------------------------------------------------------------- /src/cluster/config_rustls.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | use std::sync::Arc; 3 | use std::net; 4 | 5 | use crate::authenticators::Authenticator; 6 | 7 | /// Cluster configuration that holds per node SSL configs 8 | pub struct ClusterRustlsConfig(pub Vec>); 9 | 10 | /// Single node SSL connection config. 11 | #[derive(Clone)] 12 | pub struct NodeRustlsConfig { 13 | pub addr: net::SocketAddr, 14 | pub dns_name: webpki::DNSName, 15 | pub authenticator: A, 16 | pub max_size: u32, 17 | pub min_idle: Option, 18 | pub max_lifetime: Option, 19 | pub idle_timeout: Option, 20 | pub connection_timeout: Duration, 21 | pub config: Arc, 22 | } 23 | 24 | /// Builder structure that helps to configure SSL connection for node. 25 | pub struct NodeRustlsConfigBuilder { 26 | addr: net::SocketAddr, 27 | dns_name: webpki::DNSName, 28 | authenticator: A, 29 | max_size: Option, 30 | min_idle: Option, 31 | max_lifetime: Option, 32 | idle_timeout: Option, 33 | connection_timeout: Option, 34 | config: Arc, 35 | } 36 | 37 | impl NodeRustlsConfigBuilder { 38 | const DEFAULT_MAX_SIZE: u32 = 10; 39 | const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(30); 40 | 41 | /// `NodeRustlsConfigBuilder` constructor function. It receives 42 | /// * node socket address 43 | /// * authenticator 44 | pub fn new(addr: net::SocketAddr, dns_name: webpki::DNSName, authenticator: A, config: Arc) -> Self { 45 | NodeRustlsConfigBuilder { 46 | addr, 47 | dns_name, 48 | authenticator, 49 | max_size: None, 50 | min_idle: None, 51 | max_lifetime: None, 52 | idle_timeout: None, 53 | connection_timeout: None, 54 | config, 55 | } 56 | } 57 | 58 | /// Sets the maximum number of connections managed by the pool. 59 | /// Defaults to 10. 60 | pub fn max_size(mut self, size: u32) -> Self { 61 | self.max_size = Some(size); 62 | self 63 | } 64 | 65 | /// Sets the minimum idle connection count maintained by the pool. 66 | /// If set, the pool will try to maintain at least this many idle 67 | /// connections at all times, while respecting the value of `max_size`. 68 | /// Defaults to None (equivalent to the value of `max_size`). 69 | pub fn min_idle(mut self, min_idle: Option) -> Self { 70 | self.min_idle = min_idle; 71 | self 72 | } 73 | 74 | /// Sets the maximum lifetime of connections in the pool. 75 | /// If set, connections will be closed after existing for at most 30 seconds beyond this duration. 76 | /// If a connection reaches its maximum lifetime while checked out it will be closed when it is returned to the pool. 77 | /// Defaults to 30 minutes. 78 | pub fn max_lifetime(mut self, max_lifetime: Option) -> Self { 79 | self.max_lifetime = max_lifetime; 80 | self 81 | } 82 | 83 | /// Sets the idle timeout used by the pool. 84 | /// If set, connections will be closed after sitting idle for at most 30 seconds beyond this duration. 85 | /// Defaults to 10 minutes. 86 | pub fn idle_timeout(mut self, idle_timeout: Option) -> Self { 87 | self.idle_timeout = idle_timeout; 88 | self 89 | } 90 | 91 | /// Sets the connection timeout used by the pool. 92 | /// Defaults to 30 seconds. 93 | pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { 94 | self.connection_timeout = Some(connection_timeout); 95 | self 96 | } 97 | 98 | /// Sets new authenticator. 99 | pub fn authenticator(mut self, authenticator: A) -> Self { 100 | self.authenticator = authenticator; 101 | self 102 | } 103 | 104 | /// Finalizes building process and returns `NodeRustlsConfig` 105 | pub fn build(self) -> NodeRustlsConfig { 106 | NodeRustlsConfig { 107 | addr: self.addr, 108 | dns_name: self.dns_name, 109 | authenticator: self.authenticator, 110 | config: self.config, 111 | 112 | max_size: self.max_size.unwrap_or(Self::DEFAULT_MAX_SIZE), 113 | min_idle: self.min_idle, 114 | max_lifetime: self.max_lifetime, 115 | idle_timeout: self.idle_timeout, 116 | connection_timeout: self 117 | .connection_timeout 118 | .unwrap_or(Self::DEFAULT_CONNECTION_TIMEOUT), 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/cluster/config_ssl.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ssl")] 2 | use openssl::ssl::SslConnector; 3 | use std::time::Duration; 4 | 5 | use crate::authenticators::Authenticator; 6 | 7 | /// Cluster configuration that holds per node SSL configs 8 | pub struct ClusterSslConfig<'a, A: Authenticator + Sized>(pub Vec>); 9 | 10 | /// Single node SSL connection config. 11 | #[derive(Clone)] 12 | pub struct NodeSslConfig<'a, A> { 13 | pub addr: &'a str, 14 | pub authenticator: A, 15 | pub ssl_connector: SslConnector, 16 | pub max_size: u32, 17 | pub min_idle: Option, 18 | pub max_lifetime: Option, 19 | pub idle_timeout: Option, 20 | pub connection_timeout: Duration, 21 | } 22 | 23 | /// Builder structure that helps to configure SSL connection for node. 24 | pub struct NodeSslConfigBuilder<'a, A> { 25 | addr: &'a str, 26 | authenticator: A, 27 | ssl_connector: SslConnector, 28 | max_size: Option, 29 | min_idle: Option, 30 | max_lifetime: Option, 31 | idle_timeout: Option, 32 | connection_timeout: Option, 33 | } 34 | 35 | impl<'a, A: Authenticator + Sized> NodeSslConfigBuilder<'a, A> { 36 | const DEFAULT_MAX_SIZE: u32 = 10; 37 | const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(30); 38 | 39 | /// `NodeSslConfigBuilder` constructor function. It receives 40 | /// * node socket address as a string 41 | /// * authenticator 42 | /// * SSL connector structure (for more details see [openssl docs](https://docs.rs/openssl/0.10.12/openssl/ssl/struct.SslConnector.html)) 43 | pub fn new<'b>( 44 | addr: &'b str, 45 | authenticator: A, 46 | ssl_connector: SslConnector, 47 | ) -> NodeSslConfigBuilder<'b, A> { 48 | NodeSslConfigBuilder { 49 | addr, 50 | authenticator, 51 | ssl_connector, 52 | max_size: None, 53 | min_idle: None, 54 | max_lifetime: None, 55 | idle_timeout: None, 56 | connection_timeout: None, 57 | } 58 | } 59 | 60 | /// Sets the maximum number of connections managed by the pool. 61 | /// Defaults to 10. 62 | pub fn max_size(mut self, size: u32) -> Self { 63 | self.max_size = Some(size); 64 | self 65 | } 66 | 67 | /// Sets the minimum idle connection count maintained by the pool. 68 | /// If set, the pool will try to maintain at least this many idle 69 | /// connections at all times, while respecting the value of `max_size`. 70 | /// Defaults to None (equivalent to the value of `max_size`). 71 | pub fn min_idle(mut self, min_idle: Option) -> Self { 72 | self.min_idle = min_idle; 73 | self 74 | } 75 | 76 | /// Sets the maximum lifetime of connections in the pool. 77 | /// If set, connections will be closed after existing for at most 30 seconds beyond this duration. 78 | /// If a connection reaches its maximum lifetime while checked out it will be closed when it is returned to the pool. 79 | /// Defaults to 30 minutes. 80 | pub fn max_lifetime(mut self, max_lifetime: Option) -> Self { 81 | self.max_lifetime = max_lifetime; 82 | self 83 | } 84 | 85 | /// Sets the idle timeout used by the pool. 86 | /// If set, connections will be closed after sitting idle for at most 30 seconds beyond this duration. 87 | /// Defaults to 10 minutes. 88 | pub fn idle_timeout(mut self, idle_timeout: Option) -> Self { 89 | self.idle_timeout = idle_timeout; 90 | self 91 | } 92 | 93 | /// Sets the connection timeout used by the pool. 94 | /// Defaults to 30 seconds. 95 | pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { 96 | self.connection_timeout = Some(connection_timeout); 97 | self 98 | } 99 | 100 | /// Sets new authenticator. 101 | pub fn authenticator(mut self, authenticator: A) -> Self { 102 | self.authenticator = authenticator; 103 | self 104 | } 105 | 106 | /// Finalizes building process and returns `NodeSslConfig` 107 | pub fn build(self) -> NodeSslConfig<'a, A> { 108 | NodeSslConfig { 109 | addr: self.addr, 110 | authenticator: self.authenticator, 111 | ssl_connector: self.ssl_connector, 112 | 113 | max_size: self.max_size.unwrap_or(Self::DEFAULT_MAX_SIZE), 114 | min_idle: self.min_idle, 115 | max_lifetime: self.max_lifetime, 116 | idle_timeout: self.idle_timeout, 117 | connection_timeout: self 118 | .connection_timeout 119 | .unwrap_or(Self::DEFAULT_CONNECTION_TIMEOUT), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/cluster/config_tcp.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::authenticators::Authenticator; 4 | 5 | /// Cluster configuration that holds per node TCP configs 6 | pub struct ClusterTcpConfig<'a, A: Authenticator + Sized>(pub Vec>); 7 | 8 | /// Single node TCP connection config. 9 | #[derive(Clone)] 10 | pub struct NodeTcpConfig<'a, A> { 11 | pub addr: &'a str, 12 | pub authenticator: A, 13 | pub max_size: u32, 14 | pub min_idle: Option, 15 | pub max_lifetime: Option, 16 | pub idle_timeout: Option, 17 | pub connection_timeout: Duration, 18 | } 19 | 20 | /// Builder structure that helps to configure TCP connection for node. 21 | pub struct NodeTcpConfigBuilder<'a, A> { 22 | addr: &'a str, 23 | authenticator: A, 24 | max_size: Option, 25 | min_idle: Option, 26 | max_lifetime: Option, 27 | idle_timeout: Option, 28 | connection_timeout: Option, 29 | } 30 | 31 | impl<'a, A: Authenticator + Sized> NodeTcpConfigBuilder<'a, A> { 32 | const DEFAULT_MAX_SIZE: u32 = 10; 33 | const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(30); 34 | 35 | /// `NodeTcpConfigBuilder` constructor function. It receivesthread::spawn(move || { 36 | /// * node socket address as a string 37 | /// * authenticator 38 | pub fn new<'b>(addr: &'b str, authenticator: A) -> NodeTcpConfigBuilder<'b, A> { 39 | NodeTcpConfigBuilder { 40 | addr, 41 | authenticator, 42 | max_size: None, 43 | min_idle: None, 44 | max_lifetime: None, 45 | idle_timeout: None, 46 | connection_timeout: None, 47 | } 48 | } 49 | 50 | /// Sets the maximum number of connections managed by the pool. 51 | /// Defaults to 10. 52 | pub fn max_size(mut self, size: u32) -> Self { 53 | self.max_size = Some(size); 54 | self 55 | } 56 | 57 | /// Sets the minimum idle connection count maintained by the pool. 58 | /// If set, the pool will try to maintain at least this many idle 59 | /// connections at all times, while respecting the value of `max_size`. 60 | /// Defaults to None (equivalent to the value of `max_size`). 61 | pub fn min_idle(mut self, min_idle: Option) -> Self { 62 | self.min_idle = min_idle; 63 | self 64 | } 65 | 66 | /// Sets the maximum lifetime of connections in the pool. 67 | /// If set, connections will be closed after existing for at most 30 seconds beyond this duration. 68 | /// If a connection reaches its maximum lifetime while checked out it will be closed when it is returned to the pool. 69 | /// Defaults to 30 minutes. 70 | pub fn max_lifetime(mut self, max_lifetime: Option) -> Self { 71 | self.max_lifetime = max_lifetime; 72 | self 73 | } 74 | 75 | /// Sets the idle timeout used by the pool. 76 | /// If set, connections will be closed after sitting idle for at most 30 seconds beyond this duration. 77 | /// Defaults to 10 minutes. 78 | pub fn idle_timeout(mut self, idle_timeout: Option) -> Self { 79 | self.idle_timeout = idle_timeout; 80 | self 81 | } 82 | 83 | /// Sets the connection timeout used by the pool. 84 | /// Defaults to 30 seconds. 85 | pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { 86 | self.connection_timeout = Some(connection_timeout); 87 | self 88 | } 89 | 90 | /// Sets new authenticator. 91 | pub fn authenticator(mut self, authenticator: A) -> Self { 92 | self.authenticator = authenticator; 93 | self 94 | } 95 | 96 | /// Finalizes building process and returns `NodeSslConfig` 97 | pub fn build(self) -> NodeTcpConfig<'a, A> { 98 | NodeTcpConfig { 99 | addr: self.addr, 100 | authenticator: self.authenticator, 101 | 102 | max_size: self.max_size.unwrap_or(Self::DEFAULT_MAX_SIZE), 103 | min_idle: self.min_idle, 104 | max_lifetime: self.max_lifetime, 105 | idle_timeout: self.idle_timeout, 106 | connection_timeout: self 107 | .connection_timeout 108 | .unwrap_or(Self::DEFAULT_CONNECTION_TIMEOUT), 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/cluster/generic_connection_pool.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::sync::Arc; 3 | 4 | use r2d2; 5 | 6 | /// Generic pool connection that is able to return an 7 | /// `r2r2::Pool` as well as an IP address of a node. 8 | pub struct ConnectionPool { 9 | pool: Arc>, 10 | addr: SocketAddr, 11 | } 12 | 13 | impl ConnectionPool { 14 | pub fn new(pool: r2d2::Pool, addr: SocketAddr) -> Self { 15 | ConnectionPool { 16 | pool: Arc::new(pool), 17 | addr, 18 | } 19 | } 20 | 21 | /// Returns reference to underlying `r2d2::Pool`. 22 | pub fn get_pool(&self) -> Arc> { 23 | self.pool.clone() 24 | } 25 | 26 | /// Return an IP address. 27 | pub fn get_addr(&self) -> SocketAddr { 28 | self.addr 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/cluster/mod.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell; 3 | 4 | #[cfg(feature = "ssl")] 5 | mod config_ssl; 6 | #[cfg(feature = "rust-tls")] 7 | mod config_rustls; 8 | mod config_tcp; 9 | mod generic_connection_pool; 10 | mod pager; 11 | pub mod session; 12 | #[cfg(feature = "ssl")] 13 | mod ssl_connection_pool; 14 | #[cfg(feature = "rust-tls")] 15 | mod rustls_connection_pool; 16 | mod tcp_connection_pool; 17 | 18 | #[cfg(feature = "ssl")] 19 | pub use crate::cluster::config_ssl::{ClusterSslConfig, NodeSslConfig, NodeSslConfigBuilder}; 20 | #[cfg(feature = "rust-tls")] 21 | pub use crate::cluster::config_rustls::{ClusterRustlsConfig, NodeRustlsConfig, NodeRustlsConfigBuilder}; 22 | pub use crate::cluster::config_tcp::{ClusterTcpConfig, NodeTcpConfig, NodeTcpConfigBuilder}; 23 | pub use crate::cluster::pager::{PagerState, QueryPager, SessionPager}; 24 | #[cfg(feature = "ssl")] 25 | pub use crate::cluster::ssl_connection_pool::{ 26 | new_ssl_pool, SslConnectionPool, SslConnectionsManager, 27 | }; 28 | #[cfg(feature = "rust-tls")] 29 | pub use crate::cluster::rustls_connection_pool::{ 30 | new_rustls_pool, RustlsConnectionPool, RustlsConnectionsManager, 31 | }; 32 | pub use crate::cluster::tcp_connection_pool::{ 33 | new_tcp_pool, startup, TcpConnectionPool, TcpConnectionsManager, 34 | }; 35 | pub(crate) use generic_connection_pool::ConnectionPool; 36 | 37 | use crate::compression::Compression; 38 | use crate::error; 39 | use crate::query::{BatchExecutor, ExecExecutor, PrepareExecutor, QueryExecutor}; 40 | use crate::transport::CDRSTransport; 41 | 42 | /// `GetConnection` trait provides a unified interface for Session to get a connection 43 | /// from a load balancer 44 | pub trait GetConnection< 45 | T: CDRSTransport + Send + Sync + 'static, 46 | M: r2d2::ManageConnection, Error = error::Error>, 47 | > 48 | { 49 | /// Returns connection from a load balancer. 50 | fn get_connection(&self) -> Option>; 51 | } 52 | 53 | /// `GetCompressor` trait provides a unified interface for Session to get a compressor 54 | /// for further decompressing received data. 55 | pub trait GetCompressor<'a> { 56 | /// Returns actual compressor. 57 | fn get_compressor(&self) -> Compression; 58 | } 59 | 60 | /// `CDRSSession` trait wrap ups whole query functionality. Use it only if whole query 61 | /// machinery is needed and direct sub traits otherwise. 62 | pub trait CDRSSession< 63 | 'a, 64 | T: CDRSTransport + 'static, 65 | M: r2d2::ManageConnection, Error = error::Error>, 66 | >: 67 | GetCompressor<'static> 68 | + GetConnection 69 | + QueryExecutor 70 | + PrepareExecutor 71 | + ExecExecutor 72 | + BatchExecutor 73 | { 74 | } 75 | -------------------------------------------------------------------------------- /src/cluster/pager.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | use std::marker::PhantomData; 4 | 5 | use crate::cluster::CDRSSession; 6 | use crate::error; 7 | use crate::frame::frame_result::{RowsMetadata, RowsMetadataFlag}; 8 | use crate::query::{PreparedQuery, QueryParamsBuilder}; 9 | use crate::transport::CDRSTransport; 10 | use crate::types::rows::Row; 11 | use crate::types::CBytes; 12 | 13 | pub struct SessionPager< 14 | 'a, 15 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 16 | S: CDRSSession<'static, T, M> + 'a, 17 | T: CDRSTransport + 'static, 18 | > { 19 | page_size: i32, 20 | session: &'a S, 21 | transport_type: PhantomData<&'a T>, 22 | connection_type: PhantomData<&'a M>, 23 | } 24 | 25 | impl< 26 | 'a, 27 | 'b: 'a, 28 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 29 | S: CDRSSession<'static, T, M>, 30 | T: CDRSTransport + 'static, 31 | > SessionPager<'a, M, S, T> 32 | { 33 | pub fn new(session: &'b S, page_size: i32) -> SessionPager<'a, M, S, T> { 34 | SessionPager { 35 | session, 36 | page_size, 37 | transport_type: PhantomData, 38 | connection_type: PhantomData, 39 | } 40 | } 41 | 42 | pub fn query_with_pager_state( 43 | &'a mut self, 44 | query: Q, 45 | state: PagerState, 46 | ) -> QueryPager<'a, Q, SessionPager<'a, M, S, T>> 47 | where 48 | Q: ToString, 49 | { 50 | QueryPager { 51 | pager: self, 52 | pager_state: state, 53 | query, 54 | } 55 | } 56 | 57 | pub fn query(&'a mut self, query: Q) -> QueryPager<'a, Q, SessionPager<'a, M, S, T>> 58 | where 59 | Q: ToString, 60 | { 61 | self.query_with_pager_state(query, PagerState::new()) 62 | } 63 | 64 | pub fn exec_with_pager_state( 65 | &'a mut self, 66 | query: &'a PreparedQuery, 67 | state: PagerState, 68 | ) -> ExecPager<'a, SessionPager<'a, M, S, T>> { 69 | ExecPager { 70 | pager: self, 71 | pager_state: state, 72 | query, 73 | } 74 | } 75 | 76 | pub fn exec( 77 | &'a mut self, 78 | query: &'a PreparedQuery, 79 | ) -> ExecPager<'a, SessionPager<'a, M, S, T>> { 80 | self.exec_with_pager_state(query, PagerState::new()) 81 | } 82 | } 83 | 84 | pub struct QueryPager<'a, Q: ToString, P: 'a> { 85 | pager: &'a mut P, 86 | pager_state: PagerState, 87 | query: Q, 88 | } 89 | 90 | impl< 91 | 'a, 92 | Q: ToString, 93 | T: CDRSTransport + 'static, 94 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 95 | S: CDRSSession<'static, T, M>, 96 | > QueryPager<'a, Q, SessionPager<'a, M, S, T>> 97 | { 98 | pub fn next(&mut self) -> error::Result> { 99 | let mut params = QueryParamsBuilder::new().page_size(self.pager.page_size); 100 | if self.pager_state.cursor.is_some() { 101 | params = params.paging_state(self.pager_state.cursor.clone().unwrap()); 102 | } 103 | 104 | let body = self 105 | .pager 106 | .session 107 | .query_with_params(self.query.to_string(), params.finalize()) 108 | .and_then(|frame| frame.get_body())?; 109 | 110 | let metadata_res: error::Result = body 111 | .as_rows_metadata() 112 | .ok_or("Pager query should yield a vector of rows".into()); 113 | let metadata = metadata_res?; 114 | 115 | self.pager_state.has_more_pages = 116 | Some(RowsMetadataFlag::has_has_more_pages(metadata.flags.clone())); 117 | self.pager_state.cursor = metadata.paging_state.clone(); 118 | body.into_rows() 119 | .ok_or("Pager query should yield a vector of rows".into()) 120 | } 121 | 122 | pub fn has_more(&self) -> bool { 123 | self.pager_state.has_more_pages.unwrap_or(false) 124 | } 125 | 126 | /// This method returns a copy of pager state so 127 | /// the state may be used later for continuing paging. 128 | pub fn pager_state(&self) -> PagerState { 129 | self.pager_state.clone() 130 | } 131 | } 132 | 133 | pub struct ExecPager<'a, P: 'a> { 134 | pager: &'a mut P, 135 | pager_state: PagerState, 136 | query: &'a PreparedQuery, 137 | } 138 | 139 | impl< 140 | 'a, 141 | T: CDRSTransport + 'static, 142 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 143 | S: CDRSSession<'static, T, M>, 144 | > ExecPager<'a, SessionPager<'a, M, S, T>> 145 | { 146 | pub fn next(&mut self) -> error::Result> { 147 | let mut params = QueryParamsBuilder::new().page_size(self.pager.page_size); 148 | if self.pager_state.cursor.is_some() { 149 | params = params.paging_state(self.pager_state.cursor.clone().unwrap()); 150 | } 151 | 152 | let body = self 153 | .pager 154 | .session 155 | .exec_with_params(self.query, params.finalize()) 156 | .and_then(|frame| frame.get_body())?; 157 | 158 | let metadata_res: error::Result = body 159 | .as_rows_metadata() 160 | .ok_or("Pager query should yield a vector of rows".into()); 161 | let metadata = metadata_res?; 162 | 163 | self.pager_state.has_more_pages = 164 | Some(RowsMetadataFlag::has_has_more_pages(metadata.flags.clone())); 165 | self.pager_state.cursor = metadata.paging_state.clone(); 166 | body.into_rows() 167 | .ok_or("Pager query should yield a vector of rows".into()) 168 | } 169 | 170 | pub fn has_more(&self) -> bool { 171 | self.pager_state.has_more_pages.unwrap_or(false) 172 | } 173 | 174 | /// This method returns a copy of pager state so 175 | /// the state may be used later for continuing paging. 176 | pub fn pager_state(&self) -> PagerState { 177 | self.pager_state.clone() 178 | } 179 | } 180 | 181 | #[derive(Clone, PartialEq, Debug)] 182 | pub struct PagerState { 183 | cursor: Option, 184 | has_more_pages: Option, 185 | } 186 | 187 | impl PagerState { 188 | pub fn new() -> Self { 189 | PagerState { 190 | cursor: None, 191 | has_more_pages: None, 192 | } 193 | } 194 | 195 | pub fn with_cursor(cursor: CBytes) -> Self { 196 | PagerState { 197 | cursor: Some(cursor), 198 | has_more_pages: None, 199 | } 200 | } 201 | 202 | pub fn with_cursor_and_more_flag(cursor: CBytes, has_more: bool) -> Self { 203 | PagerState { 204 | cursor: Some(cursor), 205 | has_more_pages: Some(has_more), 206 | } 207 | } 208 | 209 | pub fn has_more(&self) -> bool { 210 | self.has_more_pages.unwrap_or(false) 211 | } 212 | 213 | pub fn get_cursor(&self) -> Option { 214 | self.cursor.clone() 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/cluster/rustls_connection_pool.rs: -------------------------------------------------------------------------------- 1 | use r2d2::{Builder, ManageConnection}; 2 | 3 | use std::net; 4 | use std::sync::Arc; 5 | use std::io::Write; 6 | use std::error::Error; 7 | use core::cell::RefCell; 8 | 9 | use crate::cluster::{startup, NodeRustlsConfig}; 10 | use crate::authenticators::Authenticator; 11 | use crate::cluster::ConnectionPool; 12 | use crate::compression::Compression; 13 | use crate::frame::parser::parse_frame; 14 | use crate::frame::{Frame, IntoBytes}; 15 | use crate::transport::{CDRSTransport, TransportRustls}; 16 | use crate::error; 17 | 18 | pub type RustlsConnectionPool = ConnectionPool>; 19 | 20 | /// `r2d2::Pool` of SSL-based CDRS connections. 21 | /// 22 | /// Used internally for SSL Session for holding connections to a specific Cassandra node. 23 | pub fn new_rustls_pool(node_config: NodeRustlsConfig) -> error::Result> { 24 | let manager = RustlsConnectionsManager::new( 25 | node_config.addr, 26 | node_config.dns_name, 27 | node_config.config, 28 | node_config.authenticator, 29 | ); 30 | 31 | let pool = Builder::new() 32 | .max_size(node_config.max_size) 33 | .min_idle(node_config.min_idle) 34 | .max_lifetime(node_config.max_lifetime) 35 | .idle_timeout(node_config.idle_timeout) 36 | .connection_timeout(node_config.connection_timeout) 37 | .build(manager) 38 | .map_err(|err| error::Error::from(err.description()))?; 39 | 40 | Ok(RustlsConnectionPool::new(pool, node_config.addr)) 41 | } 42 | 43 | /// `r2d2` connection manager. 44 | pub struct RustlsConnectionsManager { 45 | addr: net::SocketAddr, 46 | dns_name: webpki::DNSName, 47 | config: Arc, 48 | auth: A, 49 | } 50 | 51 | impl RustlsConnectionsManager { 52 | #[inline] 53 | pub fn new(addr: net::SocketAddr, dns_name: webpki::DNSName, config: Arc, auth: A) -> Self { 54 | Self { 55 | addr, 56 | dns_name, 57 | config, 58 | auth, 59 | } 60 | } 61 | } 62 | 63 | impl ManageConnection for RustlsConnectionsManager { 64 | type Connection = RefCell; 65 | type Error = error::Error; 66 | 67 | fn connect(&self) -> Result { 68 | let transport = RefCell::new(TransportRustls::new(self.addr, self.dns_name.clone(), self.config.clone())?); 69 | startup(&transport, &self.auth)?; 70 | 71 | Ok(transport) 72 | } 73 | 74 | fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> { 75 | let options_frame = Frame::new_req_options().into_cbytes(); 76 | conn.borrow_mut().write(options_frame.as_slice())?; 77 | 78 | parse_frame(conn, &Compression::None {}).map(|_| ()) 79 | } 80 | 81 | fn has_broken(&self, conn: &mut Self::Connection) -> bool { 82 | !conn.borrow().is_alive() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/cluster/ssl_connection_pool.rs: -------------------------------------------------------------------------------- 1 | use openssl::ssl::SslConnector; 2 | use r2d2::{Builder, ManageConnection, Pool}; 3 | use std::cell::RefCell; 4 | use std::error::Error; 5 | use std::io::Write; 6 | use std::net::{SocketAddr, ToSocketAddrs}; 7 | 8 | use crate::authenticators::Authenticator; 9 | use crate::cluster::ConnectionPool; 10 | use crate::cluster::{startup, NodeSslConfig}; 11 | use crate::compression::Compression; 12 | use crate::error; 13 | use crate::frame::parser::parse_frame; 14 | use crate::frame::{Frame, IntoBytes}; 15 | use crate::transport::CDRSTransport; 16 | use crate::transport::TransportTls; 17 | 18 | /// Shortcut for `r2d2::Pool` type of SSL-based CDRS connections. 19 | pub type SslConnectionPool = ConnectionPool>; 20 | 21 | /// `r2d2::Pool` of SSL-based CDRS connections. 22 | /// 23 | /// Used internally for SSL Session for holding connections to a specific Cassandra node. 24 | pub fn new_ssl_pool<'a, A: Authenticator + Send + Sync + 'static>( 25 | node_config: NodeSslConfig<'a, A>, 26 | ) -> error::Result> { 27 | let manager = SslConnectionsManager::new( 28 | node_config.addr, 29 | node_config.authenticator, 30 | node_config.ssl_connector, 31 | ); 32 | 33 | let pool = Builder::new() 34 | .max_size(node_config.max_size) 35 | .min_idle(node_config.min_idle) 36 | .max_lifetime(node_config.max_lifetime) 37 | .idle_timeout(node_config.idle_timeout) 38 | .connection_timeout(node_config.connection_timeout) 39 | .build(manager) 40 | .map_err(|err| error::Error::from(err.description()))?; 41 | 42 | let addr = node_config 43 | .addr 44 | .to_socket_addrs()? 45 | .next() 46 | .ok_or_else(|| error::Error::from("Cannot parse address"))?; 47 | 48 | Ok(SslConnectionPool::new(pool, addr)) 49 | } 50 | 51 | /// `r2d2` connection manager. 52 | pub struct SslConnectionsManager { 53 | addr: String, 54 | ssl_connector: SslConnector, 55 | auth: A, 56 | } 57 | 58 | impl SslConnectionsManager { 59 | pub fn new(addr: S, auth: A, ssl_connector: SslConnector) -> Self { 60 | SslConnectionsManager { 61 | addr: addr.to_string(), 62 | auth, 63 | ssl_connector, 64 | } 65 | } 66 | } 67 | 68 | impl ManageConnection for SslConnectionsManager { 69 | type Connection = RefCell; 70 | type Error = error::Error; 71 | 72 | fn connect(&self) -> Result { 73 | let transport = RefCell::new(TransportTls::new(&self.addr, &self.ssl_connector)?); 74 | startup(&transport, &self.auth)?; 75 | 76 | Ok(transport) 77 | } 78 | 79 | fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> { 80 | let options_frame = Frame::new_req_options().into_cbytes(); 81 | conn.borrow_mut().write(options_frame.as_slice())?; 82 | 83 | parse_frame(conn, &Compression::None {}).map(|_| ()) 84 | } 85 | 86 | fn has_broken(&self, conn: &mut Self::Connection) -> bool { 87 | !conn.borrow().is_alive() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/cluster/tcp_connection_pool.rs: -------------------------------------------------------------------------------- 1 | use r2d2::{Builder, ManageConnection}; 2 | use std::cell::RefCell; 3 | use std::io; 4 | use std::io::Write; 5 | use std::net::ToSocketAddrs; 6 | 7 | use crate::authenticators::Authenticator; 8 | use crate::cluster::ConnectionPool; 9 | use crate::cluster::NodeTcpConfig; 10 | use crate::compression::Compression; 11 | use crate::error; 12 | use crate::frame::parser::parse_frame; 13 | use crate::frame::{Frame, IntoBytes, Opcode}; 14 | use crate::transport::{CDRSTransport, TransportTcp}; 15 | 16 | /// Shortcut for `r2d2::Pool` type of TCP-based CDRS connections. 17 | pub type TcpConnectionPool = ConnectionPool>; 18 | 19 | /// `r2d2::Pool` of TCP-based CDRS connections. 20 | /// 21 | /// Used internally for TCP Session for holding connections to a specific Cassandra node. 22 | pub fn new_tcp_pool<'a, A: Authenticator + Send + Sync + 'static>( 23 | node_config: NodeTcpConfig<'a, A>, 24 | ) -> error::Result> { 25 | let manager = 26 | TcpConnectionsManager::new(node_config.addr.to_string(), node_config.authenticator); 27 | 28 | let pool = Builder::new() 29 | .max_size(node_config.max_size) 30 | .min_idle(node_config.min_idle) 31 | .max_lifetime(node_config.max_lifetime) 32 | .idle_timeout(node_config.idle_timeout) 33 | .connection_timeout(node_config.connection_timeout) 34 | .build(manager) 35 | .map_err(|err| error::Error::from(err.to_string()))?; 36 | 37 | let addr = node_config 38 | .addr 39 | .to_socket_addrs()? 40 | .next() 41 | .ok_or_else(|| error::Error::from("Cannot parse address"))?; 42 | 43 | Ok(TcpConnectionPool::new(pool, addr)) 44 | } 45 | 46 | /// `r2d2` connection manager. 47 | pub struct TcpConnectionsManager { 48 | addr: String, 49 | auth: A, 50 | } 51 | 52 | impl TcpConnectionsManager { 53 | pub fn new(addr: S, auth: A) -> Self { 54 | TcpConnectionsManager { 55 | addr: addr.to_string(), 56 | auth, 57 | } 58 | } 59 | } 60 | 61 | impl ManageConnection for TcpConnectionsManager { 62 | type Connection = RefCell; 63 | type Error = error::Error; 64 | 65 | fn connect(&self) -> Result { 66 | let transport = RefCell::new(TransportTcp::new(&self.addr)?); 67 | startup(&transport, &self.auth)?; 68 | 69 | Ok(transport) 70 | } 71 | 72 | fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> { 73 | let options_frame = Frame::new_req_options().into_cbytes(); 74 | conn.borrow_mut().write(options_frame.as_slice())?; 75 | 76 | parse_frame(conn, &Compression::None {}).map(|_| ()) 77 | } 78 | 79 | fn has_broken(&self, conn: &mut Self::Connection) -> bool { 80 | !conn.borrow().is_alive() 81 | } 82 | } 83 | 84 | pub fn startup<'b, T: CDRSTransport + 'static, A: Authenticator + 'static + Sized>( 85 | transport: &RefCell, 86 | session_authenticator: &'b A, 87 | ) -> error::Result<()> { 88 | let ref mut compression = Compression::None; 89 | let startup_frame = Frame::new_req_startup(compression.as_str()).into_cbytes(); 90 | 91 | transport.borrow_mut().write(startup_frame.as_slice())?; 92 | 93 | let start_response = parse_frame(transport, compression)?; 94 | 95 | if start_response.opcode == Opcode::Ready { 96 | return Ok(()); 97 | } 98 | 99 | if start_response.opcode == Opcode::Authenticate { 100 | let body = start_response.get_body()?; 101 | let authenticator = body.get_authenticator().expect( 102 | "Cassandra Server did communicate that it neededs 103 | authentication but the auth schema was missing in the body response", 104 | ); 105 | 106 | // This creates a new scope; avoiding a clone 107 | // and we check whether 108 | // 1. any authenticators has been passed in by client and if not send error back 109 | // 2. authenticator is provided by the client and `auth_scheme` presented by 110 | // the server and client are same if not send error back 111 | // 3. if it falls through it means the preliminary conditions are true 112 | 113 | let auth_check = session_authenticator 114 | .get_cassandra_name() 115 | .ok_or(error::Error::General( 116 | "No authenticator was provided".to_string(), 117 | )) 118 | .map(|auth| { 119 | if authenticator != auth { 120 | let io_err = io::Error::new( 121 | io::ErrorKind::NotFound, 122 | format!( 123 | "Unsupported type of authenticator. {:?} got, 124 | but {} is supported.", 125 | authenticator, auth 126 | ), 127 | ); 128 | return Err(error::Error::Io(io_err)); 129 | } 130 | Ok(()) 131 | }); 132 | 133 | if let Err(err) = auth_check { 134 | return Err(err); 135 | } 136 | 137 | let auth_token_bytes = session_authenticator.get_auth_token(); 138 | transport.borrow_mut().write( 139 | Frame::new_req_auth_response(auth_token_bytes) 140 | .into_cbytes() 141 | .as_slice(), 142 | )?; 143 | parse_frame(transport, compression)?; 144 | 145 | return Ok(()); 146 | } 147 | 148 | unreachable!(); 149 | } 150 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use std::io; 5 | use std::result; 6 | use std::string::FromUtf8Error; 7 | 8 | use crate::compression::CompressionError; 9 | use crate::frame::frame_error::CDRSError; 10 | use uuid::Error as UUIDError; 11 | 12 | pub type Result = result::Result; 13 | 14 | /// CDRS custom error type. CDRS expects two types of error - errors returned by Server 15 | /// and internal erros occured within the driver itself. Ocassionaly `io::Error` 16 | /// is a type that represent internal error because due to implementation IO errors only 17 | /// can be raised by CDRS driver. `Server` error is an error which are ones returned by 18 | /// a Server via result error frames. 19 | #[derive(Debug)] 20 | pub enum Error { 21 | /// Internal IO error. 22 | Io(io::Error), 23 | /// Internal error that may be raised during `uuid::Uuid::from_bytes` 24 | UUIDParse(UUIDError), 25 | /// General error 26 | General(String), 27 | /// Internal error that may be raised during `String::from_utf8` 28 | FromUtf8(FromUtf8Error), 29 | /// Internal Compression/Decompression error 30 | Compression(CompressionError), 31 | /// Server error. 32 | Server(CDRSError), 33 | } 34 | 35 | pub fn column_is_empty_err(column_name: T) -> Error { 36 | Error::General(format!("Column or UDT property '{}' is empty", column_name)) 37 | } 38 | 39 | impl fmt::Display for Error { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | match *self { 42 | Error::Io(ref err) => write!(f, "IO error: {}", err), 43 | Error::Compression(ref err) => write!(f, "Compressor error: {}", err), 44 | Error::Server(ref err) => write!(f, "Server error: {:?}", err.message), 45 | Error::FromUtf8(ref err) => write!(f, "FromUtf8Error error: {:?}", err), 46 | Error::UUIDParse(ref err) => write!(f, "UUIDParse error: {:?}", err), 47 | Error::General(ref err) => write!(f, "GeneralParsing error: {:?}", err), 48 | } 49 | } 50 | } 51 | 52 | impl error::Error for Error { 53 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 54 | match *self { 55 | Error::Io(ref e) => Some(e), 56 | Error::UUIDParse(ref e) => Some(e), 57 | Error::FromUtf8(ref e) => Some(e), 58 | Error::Compression(ref e) => Some(e), 59 | _ => None 60 | } 61 | } 62 | } 63 | 64 | impl From for Error { 65 | fn from(err: io::Error) -> Error { 66 | Error::Io(err) 67 | } 68 | } 69 | 70 | impl From for Error { 71 | fn from(err: CDRSError) -> Error { 72 | Error::Server(err) 73 | } 74 | } 75 | 76 | impl From for Error { 77 | fn from(err: CompressionError) -> Error { 78 | Error::Compression(err) 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(err: FromUtf8Error) -> Error { 84 | Error::FromUtf8(err) 85 | } 86 | } 87 | 88 | impl From for Error { 89 | fn from(err: UUIDError) -> Error { 90 | Error::UUIDParse(err) 91 | } 92 | } 93 | 94 | impl From for Error { 95 | fn from(err: String) -> Error { 96 | Error::General(err) 97 | } 98 | } 99 | 100 | impl<'a> From<&'a str> for Error { 101 | fn from(err: &str) -> Error { 102 | Error::General(err.to_string()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::iter::Iterator; 3 | use std::sync::mpsc::{channel, Receiver, Sender}; 4 | 5 | use crate::compression::Compression; 6 | use crate::error; 7 | use crate::frame::events::{ 8 | SchemaChange as FrameSchemaChange, ServerEvent as FrameServerEvent, 9 | SimpleServerEvent as FrameSimpleServerEvent, 10 | }; 11 | use crate::frame::parser::parse_frame; 12 | use crate::transport::CDRSTransport; 13 | 14 | /// Full Server Event which includes all details about occured change. 15 | pub type ServerEvent = FrameServerEvent; 16 | 17 | /// Simplified Server event. It should be used to represent an event 18 | /// which consumer wants listen to. 19 | pub type SimpleServerEvent = FrameSimpleServerEvent; 20 | 21 | /// Reexport of `FrameSchemaChange`. 22 | pub type SchemaChange = FrameSchemaChange; 23 | 24 | /// Factory function which returns a `Listener` and related `EventStream.` 25 | /// 26 | /// `Listener` provides only one function `start` to start listening. It 27 | /// blocks a thread so should be moved into a separate one to no release 28 | /// main thread. 29 | /// 30 | /// `EventStream` is an iterator which returns new events once they come. 31 | /// It is similar to `Receiver::iter`. 32 | pub fn new_listener(transport: X) -> (Listener, EventStream) { 33 | let (tx, rx) = channel(); 34 | let listener = Listener { 35 | transport: transport, 36 | tx: tx, 37 | }; 38 | let stream = EventStream { rx: rx }; 39 | (listener, stream) 40 | } 41 | 42 | /// `Listener` provides only one function `start` to start listening. It 43 | /// blocks a thread so should be moved into a separate one to no release 44 | /// main thread. 45 | 46 | pub struct Listener { 47 | transport: X, 48 | tx: Sender, 49 | } 50 | 51 | impl Listener> { 52 | /// It starts a process of listening to new events. Locks a frame. 53 | pub fn start(self, compressor: &Compression) -> error::Result<()> { 54 | loop { 55 | let event_opt = parse_frame(&self.transport, compressor)? 56 | .get_body()? 57 | .into_server_event(); 58 | 59 | let event = if event_opt.is_some() { 60 | // unwrap is safe as we've checked that event_opt.is_some() 61 | event_opt.unwrap().event as ServerEvent 62 | } else { 63 | continue; 64 | }; 65 | match self.tx.send(event) { 66 | Err(err) => return Err(error::Error::General(err.to_string())), 67 | _ => continue, 68 | } 69 | } 70 | } 71 | } 72 | 73 | /// `EventStream` is an iterator which returns new events once they come. 74 | /// It is similar to `Receiver::iter`. 75 | pub struct EventStream { 76 | rx: Receiver, 77 | } 78 | 79 | impl Iterator for EventStream { 80 | type Item = ServerEvent; 81 | 82 | fn next(&mut self) -> Option { 83 | self.rx.recv().ok() 84 | } 85 | } 86 | 87 | impl Into for EventStream { 88 | fn into(self) -> EventStreamNonBlocking { 89 | EventStreamNonBlocking { rx: self.rx } 90 | } 91 | } 92 | 93 | /// `EventStreamNonBlocking` is an iterator which returns new events once they come. 94 | /// It is similar to `Receiver::iter`. It's a non-blocking version of `EventStream` 95 | #[derive(Debug)] 96 | pub struct EventStreamNonBlocking { 97 | rx: Receiver, 98 | } 99 | 100 | impl Iterator for EventStreamNonBlocking { 101 | type Item = ServerEvent; 102 | 103 | fn next(&mut self) -> Option { 104 | self.rx.try_recv().ok() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/frame/frame_auth_challenge.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::frame::FromCursor; 5 | use crate::types::CBytes; 6 | 7 | /// Server authentication challenge. 8 | #[derive(Debug)] 9 | pub struct BodyResAuthChallenge { 10 | pub data: CBytes, 11 | } 12 | 13 | impl FromCursor for BodyResAuthChallenge { 14 | fn from_cursor(mut cursor: &mut Cursor<&[u8]>) -> error::Result { 15 | CBytes::from_cursor(&mut cursor).map(|data| BodyResAuthChallenge { data: data }) 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use crate::frame::traits::FromCursor; 23 | use std::io::Cursor; 24 | 25 | #[test] 26 | fn body_res_auth_challenge_from_cursor() { 27 | let few_bytes = &[0, 0, 0, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 28 | let mut cursor: Cursor<&[u8]> = Cursor::new(few_bytes); 29 | let body = BodyResAuthChallenge::from_cursor(&mut cursor).unwrap(); 30 | assert_eq!( 31 | body.data.into_plain().unwrap(), 32 | vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/frame/frame_auth_response.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::frame::*; 4 | use crate::types::CBytes; 5 | 6 | #[derive(Debug)] 7 | pub struct BodyReqAuthResponse { 8 | data: CBytes, 9 | } 10 | 11 | impl BodyReqAuthResponse { 12 | pub fn new(data: CBytes) -> BodyReqAuthResponse { 13 | BodyReqAuthResponse { data: data } 14 | } 15 | } 16 | 17 | impl IntoBytes for BodyReqAuthResponse { 18 | fn into_cbytes(&self) -> Vec { 19 | self.data.into_cbytes() 20 | } 21 | } 22 | 23 | // Frame implementation related to BodyReqStartup 24 | 25 | impl Frame { 26 | /// Creates new frame of type `AuthResponse`. 27 | pub fn new_req_auth_response(token_bytes: CBytes) -> Frame { 28 | let version = Version::Request; 29 | let flag = Flag::Ignore; 30 | let stream = rand::random::(); 31 | let opcode = Opcode::AuthResponse; 32 | let body = BodyReqAuthResponse::new(token_bytes); 33 | 34 | Frame { 35 | version: version, 36 | flags: vec![flag], 37 | stream: stream, 38 | opcode: opcode, 39 | body: body.into_cbytes(), 40 | // for request frames it's always None 41 | tracing_id: None, 42 | warnings: vec![], 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | use crate::frame::traits::IntoBytes; 51 | use crate::types::CBytes; 52 | 53 | #[test] 54 | fn body_req_auth_response() { 55 | let bytes = CBytes::new(vec![1, 2, 3]); 56 | let body = BodyReqAuthResponse::new(bytes); 57 | assert_eq!(body.into_cbytes(), vec![0, 0, 0, 3, 1, 2, 3]); 58 | } 59 | 60 | #[test] 61 | fn frame_body_req_auth_response() { 62 | let bytes = vec![1, 2, 3]; 63 | let frame = Frame::new_req_auth_response(CBytes::new(bytes)); 64 | 65 | assert_eq!(frame.version, Version::Request); 66 | assert_eq!(frame.flags, vec![Flag::Ignore]); 67 | assert_eq!(frame.opcode, Opcode::AuthResponse); 68 | assert_eq!(frame.body, &[0, 0, 0, 3, 1, 2, 3]); 69 | assert_eq!(frame.tracing_id, None); 70 | assert_eq!(frame.warnings.len(), 0); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/frame/frame_auth_success.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::frame::FromCursor; 5 | 6 | /// `BodyReqAuthSuccess` is a frame that represents a successfull authentication response. 7 | #[derive(Debug, PartialEq)] 8 | pub struct BodyReqAuthSuccess {} 9 | 10 | impl FromCursor for BodyReqAuthSuccess { 11 | fn from_cursor(mut _cursor: &mut Cursor<&[u8]>) -> error::Result { 12 | Ok(BodyReqAuthSuccess {}) 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | use crate::frame::traits::FromCursor; 20 | use std::io::Cursor; 21 | 22 | #[test] 23 | fn test_name() { 24 | let rnd_bytes = [4, 5, 3, 8, 4, 6, 5, 0, 3, 7, 2]; 25 | let mut cursor: Cursor<&[u8]> = Cursor::new(&rnd_bytes); 26 | let body = BodyReqAuthSuccess::from_cursor(&mut cursor).unwrap(); 27 | assert_eq!(body, BodyReqAuthSuccess {}); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/frame/frame_authenticate.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::frame::FromCursor; 5 | use crate::types::CString; 6 | 7 | /// A server authentication challenge. 8 | #[derive(Debug)] 9 | pub struct BodyResAuthenticate { 10 | pub data: CString, 11 | } 12 | 13 | impl FromCursor for BodyResAuthenticate { 14 | fn from_cursor(mut cursor: &mut Cursor<&[u8]>) -> error::Result { 15 | Ok(BodyResAuthenticate { 16 | data: CString::from_cursor(&mut cursor)?, 17 | }) 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | use crate::frame::traits::FromCursor; 25 | use std::io::Cursor; 26 | 27 | #[test] 28 | fn body_res_authenticate() { 29 | // string "abcde" 30 | let data = [0, 5, 97, 98, 99, 100, 101]; 31 | let mut cursor: Cursor<&[u8]> = Cursor::new(&data); 32 | let body = BodyResAuthenticate::from_cursor(&mut cursor).unwrap(); 33 | assert_eq!(body.data.as_str(), "abcde"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/frame/frame_batch.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::consistency::Consistency; 4 | use crate::frame::*; 5 | use crate::query::{QueryFlags, PreparedQuery}; 6 | use crate::query::QueryValues; 7 | use crate::types::*; 8 | 9 | /// `BodyResReady` 10 | #[derive(Debug, Clone)] 11 | pub struct BodyReqBatch { 12 | pub batch_type: BatchType, 13 | pub queries: Vec, 14 | pub consistency: Consistency, 15 | /// **IMPORTANT NOTE:** with names flag does not work and should not be used. 16 | /// https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec#L413 17 | pub query_flags: Vec, 18 | pub serial_consistency: Option, 19 | pub timestamp: Option, 20 | } 21 | 22 | impl IntoBytes for BodyReqBatch { 23 | fn into_cbytes(&self) -> Vec { 24 | let mut bytes = vec![]; 25 | 26 | bytes.push(self.batch_type.as_byte()); 27 | 28 | bytes.extend_from_slice(to_short(self.queries.len() as i16).as_slice()); 29 | 30 | bytes = self.queries.iter().fold(bytes, |mut _bytes, q| { 31 | _bytes.extend_from_slice(q.into_cbytes().as_slice()); 32 | _bytes 33 | }); 34 | 35 | bytes.extend_from_slice(self.consistency.into_cbytes().as_slice()); 36 | 37 | let flag_byte = self 38 | .query_flags 39 | .iter() 40 | .fold(0, |mut _bytes, f| _bytes | f.as_byte()); 41 | bytes.push(flag_byte); 42 | 43 | if let Some(ref serial_consistency) = self.serial_consistency { 44 | bytes.extend_from_slice(serial_consistency.into_cbytes().as_slice()); 45 | } 46 | 47 | if let Some(ref timestamp) = self.timestamp { 48 | //bytes.extend_from_slice(to_bigint(timestamp.clone()).as_slice()); 49 | bytes.extend_from_slice(to_bigint(*timestamp).as_slice()); 50 | } 51 | 52 | bytes 53 | } 54 | } 55 | 56 | /// Batch type 57 | #[derive(Debug, Clone, PartialEq)] 58 | pub enum BatchType { 59 | /// The batch will be "logged". This is equivalent to a 60 | /// normal CQL3 batch statement. 61 | Logged, 62 | /// The batch will be "unlogged". 63 | Unlogged, 64 | /// The batch will be a "counter" batch (and non-counter 65 | /// statements will be rejected). 66 | Counter, 67 | } 68 | 69 | impl FromSingleByte for BatchType { 70 | fn from_byte(byte: u8) -> BatchType { 71 | match byte { 72 | 0 => BatchType::Logged, 73 | 1 => BatchType::Unlogged, 74 | 2 => BatchType::Counter, 75 | _ => unreachable!(), 76 | } 77 | } 78 | } 79 | 80 | impl AsByte for BatchType { 81 | fn as_byte(&self) -> u8 { 82 | match *self { 83 | BatchType::Logged => 0, 84 | BatchType::Unlogged => 1, 85 | BatchType::Counter => 2, 86 | } 87 | } 88 | } 89 | 90 | /// The structure that represents a query to be batched. 91 | #[derive(Debug, Clone)] 92 | pub struct BatchQuery { 93 | /// It indicates if a query was prepared. 94 | pub is_prepared: bool, 95 | /// It contains either id of prepared query of a query itself. 96 | pub subject: BatchQuerySubj, 97 | /// It is the optional name of the following . It must be present 98 | /// if and only if the 0x40 flag is provided for the batch. 99 | /// **Important note:** this feature does not work and should not be 100 | /// used. It is specified in a way that makes it impossible for the server 101 | /// to implement. This will be fixed in a future version of the native 102 | /// protocol. See https://issues.apache.org/jira/browse/CASSANDRA-10246 for 103 | /// more details 104 | pub values: QueryValues, 105 | } 106 | 107 | /// It contains either an id of prepared query or CQL string. 108 | #[derive(Debug, Clone)] 109 | pub enum BatchQuerySubj { 110 | PreparedId(PreparedQuery), 111 | QueryString(CStringLong), 112 | } 113 | 114 | impl IntoBytes for BatchQuery { 115 | fn into_cbytes(&self) -> Vec { 116 | let mut bytes = vec![]; 117 | 118 | // kind 119 | if self.is_prepared { 120 | bytes.push(1); 121 | } else { 122 | bytes.push(0); 123 | } 124 | 125 | match self.subject { 126 | BatchQuerySubj::PreparedId(ref s) => { 127 | bytes.extend_from_slice(s.id.borrow().into_cbytes().as_slice()); 128 | } 129 | BatchQuerySubj::QueryString(ref s) => { 130 | bytes.extend_from_slice(s.into_cbytes().as_slice()); 131 | } 132 | } 133 | 134 | bytes.extend_from_slice(to_short(self.values.len() as i16).as_slice()); 135 | 136 | bytes.extend_from_slice(self.values.into_cbytes().as_slice()); 137 | 138 | bytes 139 | } 140 | } 141 | 142 | impl Frame { 143 | /// **Note:** This function should be used internally for building query request frames. 144 | pub fn new_req_batch(query: BodyReqBatch, flags: Vec) -> Frame { 145 | let version = Version::Request; 146 | let stream = rand::random::(); 147 | let opcode = Opcode::Batch; 148 | 149 | Frame { 150 | version: version, 151 | flags: flags, 152 | stream: stream, 153 | opcode: opcode, 154 | body: query.into_cbytes(), 155 | // for request frames it's always None 156 | tracing_id: None, 157 | warnings: vec![], 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/frame/frame_event.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::frame::events::ServerEvent; 5 | use crate::frame::FromCursor; 6 | 7 | #[derive(Debug)] 8 | pub struct BodyResEvent { 9 | pub event: ServerEvent, 10 | } 11 | 12 | impl FromCursor for BodyResEvent { 13 | fn from_cursor(mut cursor: &mut Cursor<&[u8]>) -> error::Result { 14 | let event = ServerEvent::from_cursor(&mut cursor)?; 15 | 16 | Ok(BodyResEvent { event: event }) 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | use crate::frame::events::*; 24 | use crate::frame::traits::FromCursor; 25 | use std::io::Cursor; 26 | 27 | #[test] 28 | fn body_res_event() { 29 | let bytes = [ 30 | // TOPOLOGY_CHANGE 31 | 0, 15, 84, 79, 80, 79, 76, 79, 71, 89, 95, 67, 72, 65, 78, 71, 69, // NEW_NODE 32 | 0, 8, 78, 69, 87, 95, 78, 79, 68, 69, // 33 | 4, 127, 0, 0, 1, 0, 0, 0, 1, // inet - 127.0.0.1:1 34 | ]; 35 | let mut cursor: Cursor<&[u8]> = Cursor::new(&bytes); 36 | let event = BodyResEvent::from_cursor(&mut cursor).unwrap().event; 37 | 38 | match event { 39 | ServerEvent::TopologyChange(ref tc) => { 40 | assert_eq!(tc.change_type, TopologyChangeType::NewNode); 41 | assert_eq!(format!("{:?}", tc.addr.addr), "127.0.0.1:1"); 42 | } 43 | _ => panic!("should be topology change event"), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/frame/frame_execute.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::frame::*; 4 | use crate::query::QueryParams; 5 | use crate::types::*; 6 | 7 | /// The structure that represents a body of a frame of type `execute`. 8 | #[derive(Debug)] 9 | pub struct BodyReqExecute<'a> { 10 | /// Id of prepared query 11 | id: &'a CBytesShort, 12 | /// Query paramaters which have the same meaning as one for `query` 13 | /// TODO: clarify if it is QueryParams or its shortened variant 14 | query_parameters: &'a QueryParams, 15 | } 16 | 17 | impl<'a> BodyReqExecute<'a> { 18 | /// The method which creates new instance of `BodyReqExecute` 19 | pub fn new<'b>(id: &'b CBytesShort, query_parameters: &'b QueryParams) -> BodyReqExecute<'b> { 20 | BodyReqExecute { 21 | id: id, 22 | query_parameters: query_parameters, 23 | } 24 | } 25 | } 26 | 27 | impl<'a> IntoBytes for BodyReqExecute<'a> { 28 | fn into_cbytes(&self) -> Vec { 29 | let mut v: Vec = vec![]; 30 | v.extend_from_slice(self.id.into_cbytes().as_slice()); 31 | v.extend_from_slice(self.query_parameters.into_cbytes().as_slice()); 32 | v 33 | } 34 | } 35 | 36 | impl Frame { 37 | /// **Note:** This function should be used internally for building query request frames. 38 | pub fn new_req_execute( 39 | id: &CBytesShort, 40 | query_parameters: &QueryParams, 41 | flags: Vec, 42 | ) -> Frame { 43 | let version = Version::Request; 44 | let stream = rand::random::(); 45 | let opcode = Opcode::Execute; 46 | debug!( 47 | "prepared statement id{:?} getting executed with parameters {:?}", 48 | id, query_parameters 49 | ); 50 | let body = BodyReqExecute::new(id, query_parameters); 51 | 52 | Frame { 53 | version: version, 54 | flags: flags, 55 | stream: stream, 56 | opcode: opcode, 57 | body: body.into_cbytes(), 58 | // for request frames it's always None 59 | tracing_id: None, 60 | warnings: vec![], 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/frame/frame_options.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::frame::*; 4 | 5 | /// The structure which represents a body of a frame of type `options`. 6 | #[derive(Debug, Default)] 7 | pub struct BodyReqOptions; 8 | 9 | impl IntoBytes for BodyReqOptions { 10 | fn into_cbytes(&self) -> Vec { 11 | vec![] 12 | } 13 | } 14 | 15 | // Frame implementation related to BodyReqStartup 16 | 17 | impl Frame { 18 | /// Creates new frame of type `options`. 19 | pub fn new_req_options() -> Frame { 20 | let version = Version::Request; 21 | let flag = Flag::Ignore; 22 | let stream = rand::random::(); 23 | let opcode = Opcode::Options; 24 | let body: BodyReqOptions = Default::default(); 25 | 26 | Frame { 27 | version: version, 28 | flags: vec![flag], 29 | stream: stream, 30 | opcode: opcode, 31 | body: body.into_cbytes(), 32 | // for request frames it's always None 33 | tracing_id: None, 34 | warnings: vec![], 35 | } 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn test_frame_options() { 45 | let frame = Frame::new_req_options(); 46 | assert_eq!(frame.version, Version::Request); 47 | assert_eq!(frame.opcode, Opcode::Options); 48 | assert_eq!(frame.body, vec![]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/frame/frame_prepare.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::frame::*; 4 | use crate::types::*; 5 | 6 | /// Struct that represents a body of a frame of type `prepare` 7 | #[derive(Debug)] 8 | pub struct BodyReqPrepare { 9 | query: CStringLong, 10 | } 11 | 12 | impl BodyReqPrepare { 13 | /// Creates new body of a frame of type `prepare` that prepares query `query`. 14 | pub fn new(query: String) -> BodyReqPrepare { 15 | BodyReqPrepare { 16 | query: CStringLong::new(query), 17 | } 18 | } 19 | } 20 | 21 | impl IntoBytes for BodyReqPrepare { 22 | fn into_cbytes(&self) -> Vec { 23 | self.query.into_cbytes() 24 | } 25 | } 26 | 27 | impl Frame { 28 | /// **Note:** This function should be used internally for building query request frames. 29 | pub fn new_req_prepare(query: String, flags: Vec) -> Frame { 30 | let version = Version::Request; 31 | let stream = rand::random::(); 32 | let opcode = Opcode::Prepare; 33 | let body = BodyReqPrepare::new(query); 34 | 35 | Frame { 36 | version: version, 37 | flags: flags, 38 | stream: stream, 39 | opcode: opcode, 40 | body: body.into_cbytes(), 41 | // for request frames it's always None 42 | tracing_id: None, 43 | warnings: vec![], 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/frame/frame_query.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | //! Contains Query Frame related functionality. 3 | use rand; 4 | 5 | use crate::consistency::Consistency; 6 | use crate::frame::*; 7 | use crate::query::{Query, QueryFlags, QueryParams, QueryValues}; 8 | use crate::types::*; 9 | 10 | /// Structure which represents body of Query request 11 | #[derive(Debug)] 12 | pub struct BodyReqQuery { 13 | /// Query string. 14 | pub query: CStringLong, 15 | /// Query parameters. 16 | pub query_params: QueryParams, 17 | } 18 | 19 | impl BodyReqQuery { 20 | // Fabric function that produces Query request body. 21 | fn new( 22 | query: String, 23 | consistency: Consistency, 24 | values: Option, 25 | with_names: Option, 26 | page_size: Option, 27 | paging_state: Option, 28 | serial_consistency: Option, 29 | timestamp: Option, 30 | ) -> BodyReqQuery { 31 | // query flags 32 | let mut flags: Vec = vec![]; 33 | if values.is_some() { 34 | flags.push(QueryFlags::Value); 35 | } 36 | if with_names.unwrap_or(false) { 37 | flags.push(QueryFlags::WithNamesForValues); 38 | } 39 | if page_size.is_some() { 40 | flags.push(QueryFlags::PageSize); 41 | } 42 | if paging_state.is_some() { 43 | flags.push(QueryFlags::WithPagingState); 44 | } 45 | if serial_consistency.is_some() { 46 | flags.push(QueryFlags::WithSerialConsistency); 47 | } 48 | if timestamp.is_some() { 49 | flags.push(QueryFlags::WithDefaultTimestamp); 50 | } 51 | 52 | BodyReqQuery { 53 | query: CStringLong::new(query), 54 | query_params: QueryParams { 55 | consistency, 56 | flags, 57 | with_names, 58 | values, 59 | page_size, 60 | paging_state, 61 | serial_consistency, 62 | timestamp, 63 | }, 64 | } 65 | } 66 | } 67 | 68 | impl IntoBytes for BodyReqQuery { 69 | fn into_cbytes(&self) -> Vec { 70 | let mut v: Vec = vec![]; 71 | v.extend_from_slice(self.query.clone().into_cbytes().as_slice()); 72 | v.extend_from_slice(self.query_params.into_cbytes().as_slice()); 73 | v 74 | } 75 | } 76 | 77 | // Frame implementation related to BodyReqStartup 78 | 79 | impl Frame { 80 | /// **Note:** This function should be used internally for building query request frames. 81 | pub fn new_req_query( 82 | query: String, 83 | consistency: Consistency, 84 | values: Option, 85 | with_names: Option, 86 | page_size: Option, 87 | paging_state: Option, 88 | serial_consistency: Option, 89 | timestamp: Option, 90 | flags: Vec, 91 | ) -> Frame { 92 | let version = Version::Request; 93 | let stream = rand::random::(); 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 | ); 105 | 106 | Frame { 107 | version: version, 108 | flags: flags, 109 | stream: stream, 110 | opcode: opcode, 111 | body: body.into_cbytes(), 112 | // for request frames it's always None 113 | tracing_id: None, 114 | warnings: vec![], 115 | } 116 | } 117 | 118 | /// **Note:** This function should be used internally for building query request frames. 119 | pub fn new_query(query: Query, flags: Vec) -> Frame { 120 | Frame::new_req_query( 121 | query.query, 122 | query.params.consistency, 123 | query.params.values, 124 | query.params.with_names, 125 | query.params.page_size, 126 | query.params.paging_state, 127 | query.params.serial_consistency, 128 | query.params.timestamp, 129 | flags, 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/frame/frame_ready.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | 3 | use crate::frame::IntoBytes; 4 | 5 | #[derive(Debug, PartialEq, Default)] 6 | pub struct BodyResReady; 7 | 8 | impl From> for BodyResReady { 9 | fn from(_vec: Vec) -> BodyResReady { 10 | BodyResReady {} 11 | } 12 | } 13 | 14 | impl IntoBytes for BodyResReady { 15 | fn into_cbytes(&self) -> Vec { 16 | vec![] 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | use crate::frame::traits::IntoBytes; 24 | 25 | #[test] 26 | fn body_res_ready_new() { 27 | let body: BodyResReady = Default::default(); 28 | assert_eq!(body, BodyResReady {}); 29 | } 30 | 31 | #[test] 32 | fn body_res_ready_into_cbytes() { 33 | let body = BodyResReady {}; 34 | assert_eq!(body.into_cbytes(), vec![] as Vec); 35 | } 36 | 37 | #[test] 38 | fn body_res_ready_from() { 39 | let body = BodyResReady::from(vec![]); 40 | assert_eq!(body, BodyResReady {}); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/frame/frame_register.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use crate::frame::events::SimpleServerEvent; 4 | use crate::frame::*; 5 | use crate::types::{CString, CStringList}; 6 | 7 | /// The structure which represents a body of a frame of type `options`. 8 | pub struct BodyReqRegister { 9 | pub events: Vec, 10 | } 11 | 12 | impl IntoBytes for BodyReqRegister { 13 | fn into_cbytes(&self) -> Vec { 14 | let events_string_list = CStringList { 15 | list: self 16 | .events 17 | .iter() 18 | .map(|event| CString::new(event.as_string())) 19 | .collect(), 20 | }; 21 | events_string_list.into_cbytes() 22 | } 23 | } 24 | 25 | // Frame implementation related to BodyReqRegister 26 | 27 | impl Frame { 28 | /// Creates new frame of type `REGISTER`. 29 | pub fn new_req_register(events: Vec) -> Frame { 30 | let version = Version::Request; 31 | let flag = Flag::Ignore; 32 | let stream = rand::random::(); 33 | let opcode = Opcode::Register; 34 | let register_body = BodyReqRegister { events: events }; 35 | 36 | Frame { 37 | version: version, 38 | flags: vec![flag], 39 | stream: stream, 40 | opcode: opcode, 41 | body: register_body.into_cbytes(), 42 | // for request frames it's always None 43 | tracing_id: None, 44 | warnings: vec![], 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/frame/frame_response.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::frame::frame_auth_challenge::*; 5 | use crate::frame::frame_auth_success::BodyReqAuthSuccess; 6 | use crate::frame::frame_authenticate::BodyResAuthenticate; 7 | use crate::frame::frame_error::CDRSError; 8 | use crate::frame::frame_event::BodyResEvent; 9 | use crate::frame::frame_result::{ 10 | BodyResResultPrepared, BodyResResultRows, BodyResResultSetKeyspace, BodyResResultVoid, 11 | ResResultBody, RowsMetadata, 12 | }; 13 | use crate::frame::frame_supported::*; 14 | use crate::frame::FromCursor; 15 | use crate::frame::Opcode; 16 | use crate::types::rows::Row; 17 | 18 | #[derive(Debug)] 19 | pub enum ResponseBody { 20 | Error(CDRSError), 21 | Startup, 22 | Ready(BodyResResultVoid), 23 | Authenticate(BodyResAuthenticate), 24 | Options, 25 | Supported(BodyResSupported), 26 | Query, 27 | Result(ResResultBody), 28 | Prepare, 29 | Execute, 30 | Register, 31 | Event(BodyResEvent), 32 | Batch, 33 | AuthChallenge(BodyResAuthChallenge), 34 | AuthResponse, 35 | AuthSuccess(BodyReqAuthSuccess), 36 | } 37 | 38 | impl ResponseBody { 39 | pub fn from(bytes: &[u8], response_type: &Opcode) -> error::Result { 40 | let mut cursor: Cursor<&[u8]> = Cursor::new(bytes); 41 | Ok(match *response_type { 42 | // request frames 43 | Opcode::Startup => unreachable!(), 44 | Opcode::Options => unreachable!(), 45 | Opcode::Query => unreachable!(), 46 | Opcode::Prepare => unreachable!(), 47 | Opcode::Execute => unreachable!(), 48 | Opcode::Register => unreachable!(), 49 | Opcode::Batch => unreachable!(), 50 | Opcode::AuthResponse => unreachable!(), 51 | 52 | // response frames 53 | Opcode::Error => ResponseBody::Error(CDRSError::from_cursor(&mut cursor)?), 54 | Opcode::Ready => ResponseBody::Ready(BodyResResultVoid::from_cursor(&mut cursor)?), 55 | Opcode::Authenticate => { 56 | ResponseBody::Authenticate(BodyResAuthenticate::from_cursor(&mut cursor)?) 57 | } 58 | Opcode::Supported => { 59 | ResponseBody::Supported(BodyResSupported::from_cursor(&mut cursor)?) 60 | } 61 | Opcode::Result => ResponseBody::Result(ResResultBody::from_cursor(&mut cursor)?), 62 | Opcode::Event => ResponseBody::Event(BodyResEvent::from_cursor(&mut cursor)?), 63 | Opcode::AuthChallenge => { 64 | ResponseBody::AuthChallenge(BodyResAuthChallenge::from_cursor(&mut cursor)?) 65 | } 66 | Opcode::AuthSuccess => { 67 | ResponseBody::AuthSuccess(BodyReqAuthSuccess::from_cursor(&mut cursor)?) 68 | } 69 | }) 70 | } 71 | 72 | pub fn into_rows(self) -> Option> { 73 | match self { 74 | ResponseBody::Result(res) => res.into_rows(), 75 | _ => None, 76 | } 77 | } 78 | 79 | pub fn as_rows_metadata(&self) -> Option { 80 | match *self { 81 | ResponseBody::Result(ref res) => res.as_rows_metadata(), 82 | _ => None, 83 | } 84 | } 85 | 86 | pub fn as_cols(&self) -> Option<&BodyResResultRows> { 87 | match *self { 88 | ResponseBody::Result(ref res) => match res { 89 | &ResResultBody::Rows(ref rows) => Some(rows), 90 | _ => None, 91 | }, 92 | _ => None, 93 | } 94 | } 95 | 96 | /// It unwraps body and returns BodyResResultPrepared which contains an exact result of 97 | /// PREPARE query. If frame body is not of type `Result` this method returns `None`. 98 | pub fn into_prepared(self) -> Option { 99 | match self { 100 | ResponseBody::Result(res) => res.into_prepared(), 101 | _ => None, 102 | } 103 | } 104 | 105 | /// It unwraps body and returns BodyResResultPrepared which contains an exact result of 106 | /// use keyspace query. If frame body is not of type `Result` this method returns `None`. 107 | pub fn into_set_keyspace(self) -> Option { 108 | match self { 109 | ResponseBody::Result(res) => res.into_set_keyspace(), 110 | _ => None, 111 | } 112 | } 113 | 114 | /// It unwraps body and returns BodyResEvent. 115 | /// If frame body is not of type `Result` this method returns `None`. 116 | pub fn into_server_event(self) -> Option { 117 | match self { 118 | ResponseBody::Event(event) => Some(event), 119 | _ => None, 120 | } 121 | } 122 | 123 | pub fn get_authenticator<'a>(&'a self) -> Option<&'a str> { 124 | match *self { 125 | ResponseBody::Authenticate(ref auth) => Some(auth.data.as_str()), 126 | _ => None, 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/frame/frame_startup.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rand; 4 | 5 | use crate::frame::*; 6 | use crate::types::to_short; 7 | 8 | const CQL_VERSION: &'static str = "CQL_VERSION"; 9 | const CQL_VERSION_VAL: &'static str = "3.0.0"; 10 | const COMPRESSION: &'static str = "COMPRESSION"; 11 | 12 | #[derive(Debug)] 13 | pub struct BodyReqStartup<'a> { 14 | pub map: HashMap<&'static str, &'a str>, 15 | } 16 | 17 | impl<'a> BodyReqStartup<'a> { 18 | pub fn new<'b>(compression: Option<&'b str>) -> BodyReqStartup<'b> { 19 | let mut map = HashMap::new(); 20 | map.insert(CQL_VERSION, CQL_VERSION_VAL); 21 | if let Some(c) = compression { 22 | map.insert(COMPRESSION, c); 23 | } 24 | BodyReqStartup { map: map } 25 | } 26 | 27 | // should be [u8; 2] 28 | // Number of key-value pairs 29 | fn num(&self) -> Vec { 30 | to_short(self.map.len() as i16) 31 | } 32 | } 33 | 34 | impl<'a> IntoBytes for BodyReqStartup<'a> { 35 | fn into_cbytes(&self) -> Vec { 36 | let mut v = vec![]; 37 | // push number of key-value pairs 38 | v.extend_from_slice(&self.num().as_slice()); 39 | for (key, val) in self.map.iter() { 40 | // push key len 41 | v.extend_from_slice(to_short(key.len() as i16).as_slice()); 42 | // push key itself 43 | v.extend_from_slice(key.as_bytes()); 44 | // push val len 45 | v.extend_from_slice(to_short(val.len() as i16).as_slice()); 46 | // push val itself 47 | v.extend_from_slice(val.as_bytes()); 48 | } 49 | v 50 | } 51 | } 52 | 53 | // Frame implementation related to BodyReqStartup 54 | 55 | impl Frame { 56 | /// Creates new frame of type `startup`. 57 | pub fn new_req_startup(compression: Option<&str>) -> Frame { 58 | let version = Version::Request; 59 | let flag = Flag::Ignore; 60 | let stream = rand::random::(); 61 | let opcode = Opcode::Startup; 62 | let body = BodyReqStartup::new(compression); 63 | 64 | Frame { 65 | version: version, 66 | flags: vec![flag], 67 | stream: stream, 68 | opcode: opcode, 69 | body: body.into_cbytes(), 70 | // for request frames it's always None 71 | tracing_id: None, 72 | warnings: vec![], 73 | } 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod test { 79 | use super::*; 80 | use crate::frame::{Flag, Frame, Opcode, Version}; 81 | 82 | #[test] 83 | fn new_body_req_startup_some_compression() { 84 | let compression = "test_compression"; 85 | let body = BodyReqStartup::new(Some(compression)); 86 | assert_eq!(body.map.get("CQL_VERSION"), Some(&"3.0.0")); 87 | assert_eq!(body.map.get("COMPRESSION"), Some(&compression)); 88 | assert_eq!(body.map.len(), 2); 89 | } 90 | 91 | #[test] 92 | fn new_body_req_startup_none_compression() { 93 | let body = BodyReqStartup::new(None); 94 | assert_eq!(body.map.get("CQL_VERSION"), Some(&"3.0.0")); 95 | assert_eq!(body.map.len(), 1); 96 | } 97 | 98 | #[test] 99 | fn new_req_startup() { 100 | let compression = Some("test_compression"); 101 | let frame = Frame::new_req_startup(compression); 102 | assert_eq!(frame.version, Version::Request); 103 | assert_eq!(frame.flags, vec![Flag::Ignore]); 104 | assert_eq!(frame.opcode, Opcode::Startup); 105 | assert_eq!(frame.tracing_id, None); 106 | assert_eq!(frame.warnings, vec![] as Vec); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/frame/frame_supported.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::io::Cursor; 3 | 4 | use crate::error; 5 | use crate::frame::FromCursor; 6 | use crate::types::{cursor_next_value, try_from_bytes, CString, CStringList, SHORT_LEN}; 7 | 8 | #[derive(Debug)] 9 | pub struct BodyResSupported { 10 | pub data: HashMap>, 11 | } 12 | 13 | impl FromCursor for BodyResSupported { 14 | fn from_cursor(mut cursor: &mut Cursor<&[u8]>) -> error::Result { 15 | let l = 16 | try_from_bytes(cursor_next_value(&mut cursor, SHORT_LEN as u64)?.as_slice())? as usize; 17 | let mut data: HashMap> = HashMap::with_capacity(l); 18 | for _ in 0..l { 19 | let name = CString::from_cursor(&mut cursor)?.into_plain(); 20 | let val = CStringList::from_cursor(&mut cursor)?.into_plain(); 21 | data.insert(name, val); 22 | } 23 | 24 | Ok(BodyResSupported { data: 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 test_name() { 36 | let bytes = [ 37 | 0, 1, // n options 38 | // 1-st option 39 | 0, 2, 97, 98, // key [string] "ab" 40 | 0, 2, 0, 1, 97, 0, 1, 98, /* value ["a", "b"] */ 41 | ]; 42 | let mut cursor: Cursor<&[u8]> = Cursor::new(&bytes); 43 | let options = BodyResSupported::from_cursor(&mut cursor).unwrap().data; 44 | assert_eq!(options.len(), 1); 45 | let option_ab = options.get(&"ab".to_string()).unwrap(); 46 | assert_eq!(option_ab[0], "a".to_string()); 47 | assert_eq!(option_ab[1], "b".to_string()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/frame/parser.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | use std::io::{Cursor, Read}; 4 | use std::ops::Deref; 5 | 6 | use super::*; 7 | use crate::compression::Compression; 8 | use crate::error; 9 | use crate::frame::frame_response::ResponseBody; 10 | use crate::frame::FromCursor; 11 | use crate::transport::CDRSTransport; 12 | use crate::types::data_serialization_types::decode_timeuuid; 13 | use crate::types::{from_bytes, from_u16_bytes, CStringList, UUID_LEN}; 14 | 15 | pub fn from_connection( 16 | conn: &r2d2::PooledConnection, 17 | compressor: &Compression, 18 | ) -> error::Result 19 | where 20 | T: CDRSTransport + 'static, 21 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 22 | { 23 | parse_frame(conn.deref(), compressor) 24 | } 25 | 26 | pub fn parse_frame(cursor_cell: &RefCell, compressor: &Compression) -> error::Result { 27 | let mut version_bytes = [0; Version::BYTE_LENGTH]; 28 | let mut flag_bytes = [0; Flag::BYTE_LENGTH]; 29 | let mut opcode_bytes = [0; Opcode::BYTE_LENGTH]; 30 | let mut stream_bytes = [0; STREAM_LEN]; 31 | let mut length_bytes = [0; LENGTH_LEN]; 32 | let mut cursor = cursor_cell.borrow_mut(); 33 | 34 | // NOTE: order of reads matters 35 | cursor.read_exact(&mut version_bytes)?; 36 | cursor.read_exact(&mut flag_bytes)?; 37 | cursor.read_exact(&mut stream_bytes)?; 38 | cursor.read_exact(&mut opcode_bytes)?; 39 | cursor.read_exact(&mut length_bytes)?; 40 | 41 | let version = Version::from(version_bytes.to_vec()); 42 | let flags = Flag::get_collection(flag_bytes[0]); 43 | let stream = from_u16_bytes(&stream_bytes); 44 | let opcode = Opcode::from(opcode_bytes[0]); 45 | let length = from_bytes(&length_bytes) as usize; 46 | 47 | let mut body_bytes = Vec::with_capacity(length); 48 | unsafe { 49 | body_bytes.set_len(length); 50 | } 51 | 52 | cursor.read_exact(&mut body_bytes)?; 53 | 54 | let full_body = if flags.iter().any(|flag| flag == &Flag::Compression) { 55 | compressor.decode(body_bytes)? 56 | } else { 57 | Compression::None.decode(body_bytes)? 58 | }; 59 | 60 | // Use cursor to get tracing id, warnings and actual body 61 | let mut body_cursor = Cursor::new(full_body.as_slice()); 62 | 63 | let tracing_id = if flags.iter().any(|flag| flag == &Flag::Tracing) { 64 | let mut tracing_bytes = Vec::with_capacity(UUID_LEN); 65 | unsafe { 66 | tracing_bytes.set_len(UUID_LEN); 67 | } 68 | body_cursor.read_exact(&mut tracing_bytes)?; 69 | 70 | decode_timeuuid(tracing_bytes.as_slice()).ok() 71 | } else { 72 | None 73 | }; 74 | 75 | let warnings = if flags.iter().any(|flag| flag == &Flag::Warning) { 76 | CStringList::from_cursor(&mut body_cursor)?.into_plain() 77 | } else { 78 | vec![] 79 | }; 80 | 81 | let mut body = vec![]; 82 | 83 | body_cursor.read_to_end(&mut body)?; 84 | 85 | let frame = Frame { 86 | version: version, 87 | flags: flags, 88 | opcode: opcode, 89 | stream: stream, 90 | body: body, 91 | tracing_id: tracing_id, 92 | warnings: warnings, 93 | }; 94 | 95 | convert_frame_into_result(frame) 96 | } 97 | 98 | fn convert_frame_into_result(frame: Frame) -> error::Result { 99 | match frame.opcode { 100 | Opcode::Error => frame.get_body().and_then(|err| match err { 101 | ResponseBody::Error(err) => Err(error::Error::Server(err)), 102 | _ => unreachable!(), 103 | }), 104 | _ => Ok(frame), 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/frame/traits.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use crate::error; 4 | use crate::query; 5 | 6 | /// `IntoBytes` should be used to convert a structure into array of bytes. 7 | pub trait IntoBytes { 8 | /// It should convert a struct into an array of bytes. 9 | fn into_cbytes(&self) -> Vec; 10 | } 11 | 12 | /// `FromBytes` should be used to parse an array of bytes into a structure. 13 | pub trait FromBytes { 14 | /// It gets and array of bytes and should return an implementor struct. 15 | fn from_bytes(bytes: &[u8]) -> error::Result 16 | where 17 | Self: Sized; 18 | } 19 | 20 | /// `AsBytes` should be used to convert a value into a single byte. 21 | pub trait AsByte { 22 | /// It should represent a struct as a single byte. 23 | fn as_byte(&self) -> u8; 24 | } 25 | 26 | /// `FromSingleByte` should be used to convert a single byte into a value. 27 | /// It is opposite to `AsByte`. 28 | pub trait FromSingleByte { 29 | /// It should convert a single byte into an implementor struct. 30 | fn from_byte(byte: u8) -> Self; 31 | } 32 | 33 | /// `FromCursor` should be used to get parsed structure from an `io:Cursor` 34 | /// wich bound to an array of bytes. 35 | pub trait FromCursor { 36 | /// It should return an implementor from an `io::Cursor` over an array of bytes. 37 | fn from_cursor(cursor: &mut Cursor<&[u8]>) -> error::Result 38 | where 39 | Self: Sized; 40 | } 41 | 42 | /// The trait that allows transformation of `Self` to CDRS query values. 43 | pub trait IntoQueryValues { 44 | fn into_query_values(self) -> query::QueryValues; 45 | } 46 | 47 | pub trait TryFromRow: Sized { 48 | fn try_from_row(row: crate::types::rows::Row) -> error::Result; 49 | } 50 | 51 | pub trait TryFromUDT: Sized { 52 | fn try_from_udt(udt: crate::types::udt::UDT) -> error::Result; 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **cdrs** is a native Cassandra DB client written in Rust. 2 | 3 | extern crate byteorder; 4 | extern crate snap; 5 | #[macro_use] 6 | pub mod macros; 7 | 8 | #[macro_use] 9 | extern crate log; 10 | extern crate lz4_compress; 11 | #[cfg(feature = "ssl")] 12 | extern crate openssl; 13 | #[cfg(feature = "rust-tls")] 14 | extern crate rustls; 15 | extern crate r2d2; 16 | extern crate rand; 17 | extern crate time; 18 | extern crate uuid; 19 | 20 | pub mod cluster; 21 | pub mod frame; 22 | pub mod load_balancing; 23 | pub mod query; 24 | pub mod types; 25 | 26 | pub mod authenticators; 27 | pub mod compression; 28 | pub mod consistency; 29 | pub mod error; 30 | pub mod events; 31 | pub mod transport; 32 | 33 | pub type Error = error::Error; 34 | pub type Result = error::Result; 35 | -------------------------------------------------------------------------------- /src/load_balancing/mod.rs: -------------------------------------------------------------------------------- 1 | mod random; 2 | mod round_robin; 3 | mod round_robin_sync; 4 | mod single_node; 5 | 6 | pub use crate::load_balancing::random::Random; 7 | pub use crate::load_balancing::round_robin::RoundRobin; 8 | pub use crate::load_balancing::round_robin_sync::RoundRobinSync; 9 | pub use crate::load_balancing::single_node::SingleNode; 10 | 11 | pub trait LoadBalancingStrategy: Sized { 12 | fn init(&mut self, cluster: Vec); 13 | fn next(&self) -> Option<&N>; 14 | fn remove_node(&mut self, _filter: F) 15 | where 16 | F: FnMut(&N) -> bool, 17 | { 18 | // default implementation does nothing 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/load_balancing/random.rs: -------------------------------------------------------------------------------- 1 | use rand; 2 | 3 | use super::LoadBalancingStrategy; 4 | 5 | pub struct Random { 6 | pub cluster: Vec, 7 | } 8 | 9 | impl Random { 10 | pub fn new(cluster: Vec) -> Self { 11 | Random { cluster } 12 | } 13 | 14 | /// Returns a random number from a range 15 | fn rnd_idx(bounds: (usize, usize)) -> usize { 16 | let min = bounds.0; 17 | let max = bounds.1; 18 | let rnd = rand::random::(); 19 | rnd % (max - min) + min 20 | } 21 | } 22 | 23 | impl From> for Random { 24 | fn from(cluster: Vec) -> Random { 25 | Random { cluster } 26 | } 27 | } 28 | 29 | impl LoadBalancingStrategy for Random { 30 | fn init(&mut self, cluster: Vec) { 31 | self.cluster = cluster; 32 | } 33 | 34 | /// Returns next random node from a cluster 35 | fn next(&self) -> Option<&N> { 36 | let len = self.cluster.len(); 37 | if len == 0 { 38 | return None; 39 | } 40 | self.cluster.get(Self::rnd_idx((0, len))) 41 | } 42 | 43 | fn remove_node(&mut self, filter: F) 44 | where 45 | F: FnMut(&N) -> bool, 46 | { 47 | if let Some(i) = self.cluster.iter().position(filter) { 48 | self.cluster.remove(i); 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[test] 58 | fn next_random() { 59 | let nodes = vec!["a", "b", "c", "d", "e", "f", "g"]; 60 | let load_balancer = Random::from(nodes); 61 | for _ in 0..100 { 62 | let s = load_balancer.next(); 63 | assert!(s.is_some()); 64 | } 65 | } 66 | 67 | #[test] 68 | fn remove_from_random() { 69 | let nodes = vec!["a"]; 70 | let mut load_balancer = Random::from(nodes); 71 | 72 | let s = load_balancer.next(); 73 | assert!(s.is_some()); 74 | 75 | load_balancer.remove_node(|n| n == &"a"); 76 | let s = load_balancer.next(); 77 | assert!(s.is_none()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/load_balancing/round_robin.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use super::LoadBalancingStrategy; 4 | 5 | pub struct RoundRobin { 6 | cluster: Vec, 7 | prev_idx: RefCell, 8 | } 9 | 10 | impl RoundRobin { 11 | pub fn new() -> Self { 12 | RoundRobin { 13 | prev_idx: RefCell::new(0), 14 | cluster: vec![], 15 | } 16 | } 17 | } 18 | 19 | impl From> for RoundRobin { 20 | fn from(cluster: Vec) -> RoundRobin { 21 | RoundRobin { 22 | prev_idx: RefCell::new(0), 23 | cluster: cluster, 24 | } 25 | } 26 | } 27 | 28 | impl LoadBalancingStrategy for RoundRobin { 29 | fn init(&mut self, cluster: Vec) { 30 | self.cluster = cluster; 31 | } 32 | 33 | /// Returns next node from a cluster 34 | fn next(&self) -> Option<&N> { 35 | let prev_idx = *self.prev_idx.borrow(); 36 | let next_idx = (prev_idx + 1) % self.cluster.len(); 37 | self.prev_idx.replace(next_idx); 38 | self.cluster.get(next_idx) 39 | } 40 | 41 | fn remove_node(&mut self, filter: F) 42 | where 43 | F: FnMut(&N) -> bool, 44 | { 45 | if let Some(i) = self.cluster.iter().position(filter) { 46 | self.cluster.remove(i); 47 | } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn round_robin() { 57 | let nodes = vec!["a", "b", "c"]; 58 | let nodes_c = nodes.clone(); 59 | let load_balancer = RoundRobin::from(nodes); 60 | for i in 0..10 { 61 | assert_eq!(&nodes_c[(i + 1) % 3], load_balancer.next().unwrap()); 62 | } 63 | } 64 | 65 | #[test] 66 | fn remove_from_round_robin() { 67 | let nodes = vec!["a", "b"]; 68 | let mut load_balancer = RoundRobin::from(nodes); 69 | assert_eq!(&"b", load_balancer.next().unwrap()); 70 | 71 | load_balancer.remove_node(|n| n == &"a"); 72 | assert_eq!(&"b", load_balancer.next().unwrap()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/load_balancing/round_robin_sync.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use super::LoadBalancingStrategy; 4 | 5 | pub struct RoundRobinSync { 6 | cluster: Vec, 7 | prev_idx: Mutex, 8 | } 9 | 10 | impl RoundRobinSync { 11 | pub fn new() -> Self { 12 | RoundRobinSync { 13 | prev_idx: Mutex::new(0), 14 | cluster: vec![], 15 | } 16 | } 17 | } 18 | 19 | impl From> for RoundRobinSync { 20 | fn from(cluster: Vec) -> RoundRobinSync { 21 | RoundRobinSync { 22 | prev_idx: Mutex::new(0), 23 | cluster: cluster, 24 | } 25 | } 26 | } 27 | 28 | impl LoadBalancingStrategy for RoundRobinSync { 29 | fn init(&mut self, cluster: Vec) { 30 | self.cluster = cluster; 31 | } 32 | 33 | /// Returns next node from a cluster 34 | fn next(&self) -> Option<&N> { 35 | let mut prev_idx = self.prev_idx.lock(); 36 | if let Ok(ref mut mtx) = prev_idx { 37 | let next_idx = (**mtx + 1) % self.cluster.len(); 38 | **mtx = next_idx; 39 | self.cluster.get(next_idx) 40 | } else { 41 | return None; 42 | } 43 | } 44 | 45 | fn remove_node(&mut self, filter: F) 46 | where 47 | F: FnMut(&N) -> bool, 48 | { 49 | if let Some(i) = self.cluster.iter().position(filter) { 50 | self.cluster.remove(i); 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn next_round_robin() { 61 | let nodes = vec!["a", "b", "c"]; 62 | let nodes_c = nodes.clone(); 63 | let load_balancer = RoundRobinSync::from(nodes); 64 | for i in 0..10 { 65 | assert_eq!(&nodes_c[(i + 1) % 3], load_balancer.next().unwrap()); 66 | } 67 | } 68 | 69 | #[test] 70 | fn remove_from_round_robin() { 71 | let nodes = vec!["a", "b"]; 72 | let mut load_balancer = RoundRobinSync::from(nodes); 73 | assert_eq!(&"b", load_balancer.next().unwrap()); 74 | 75 | load_balancer.remove_node(|n| n == &"a"); 76 | assert_eq!(&"b", load_balancer.next().unwrap()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/load_balancing/single_node.rs: -------------------------------------------------------------------------------- 1 | use super::LoadBalancingStrategy; 2 | 3 | pub struct SingleNode { 4 | cluster: Vec, 5 | } 6 | 7 | impl SingleNode { 8 | pub fn new() -> Self { 9 | SingleNode { cluster: vec![] } 10 | } 11 | } 12 | 13 | impl From> for SingleNode { 14 | fn from(cluster: Vec) -> SingleNode { 15 | SingleNode { cluster: cluster } 16 | } 17 | } 18 | 19 | impl LoadBalancingStrategy for SingleNode { 20 | fn init(&mut self, cluster: Vec) { 21 | self.cluster = cluster; 22 | } 23 | 24 | /// Returns first node from a cluster 25 | fn next(&self) -> Option<&N> { 26 | self.cluster.get(0) 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn single_node() { 36 | let nodes = vec!["a"]; 37 | let nodes_c = nodes.clone(); 38 | let load_balancer = SingleNode::from(nodes); 39 | assert_eq!(&nodes_c[0], load_balancer.next().unwrap()); 40 | // and one more time to check 41 | assert_eq!(&nodes_c[0], load_balancer.next().unwrap()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/query/batch_executor.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | 4 | use crate::cluster::{GetCompressor, GetConnection}; 5 | use crate::error; 6 | use crate::frame::traits::IntoBytes; 7 | use crate::frame::Frame; 8 | use crate::query::batch_query_builder::QueryBatch; 9 | use crate::transport::CDRSTransport; 10 | 11 | use super::utils::{prepare_flags, send_frame}; 12 | 13 | pub trait BatchExecutor< 14 | T: CDRSTransport + 'static, 15 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 16 | >: GetConnection + GetCompressor<'static> 17 | { 18 | fn batch_with_params_tw( 19 | &self, 20 | batch: QueryBatch, 21 | with_tracing: bool, 22 | with_warnings: bool, 23 | ) -> error::Result 24 | where 25 | Self: Sized, 26 | { 27 | let flags = prepare_flags(with_tracing, with_warnings); 28 | 29 | let query_frame = Frame::new_req_batch(batch, flags).into_cbytes(); 30 | 31 | send_frame(self, query_frame) 32 | } 33 | 34 | fn batch_with_params(&self, batch: QueryBatch) -> error::Result 35 | where 36 | Self: Sized, 37 | { 38 | self.batch_with_params_tw(batch, false, false) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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::frame_batch::{BatchQuery, BatchQuerySubj, BatchType, BodyReqBatch}; 4 | use crate::query::{QueryFlags, QueryValues, PreparedQuery}; 5 | use crate::types::CStringLong; 6 | 7 | pub type QueryBatch = BodyReqBatch; 8 | 9 | #[derive(Debug)] 10 | pub struct BatchQueryBuilder { 11 | batch_type: BatchType, 12 | queries: Vec, 13 | consistency: Consistency, 14 | serial_consistency: Option, 15 | timestamp: Option, 16 | } 17 | 18 | impl BatchQueryBuilder { 19 | pub fn new() -> BatchQueryBuilder { 20 | BatchQueryBuilder { 21 | batch_type: BatchType::Logged, 22 | queries: vec![], 23 | consistency: Consistency::One, 24 | serial_consistency: None, 25 | timestamp: None, 26 | } 27 | } 28 | 29 | pub fn batch_type(mut self, batch_type: BatchType) -> Self { 30 | self.batch_type = batch_type; 31 | self 32 | } 33 | 34 | /// Add a query (non-prepared one) 35 | pub fn add_query>(mut self, query: T, values: QueryValues) -> Self { 36 | self.queries.push(BatchQuery { 37 | is_prepared: false, 38 | subject: BatchQuerySubj::QueryString(CStringLong::new(query.into())), 39 | values, 40 | }); 41 | self 42 | } 43 | 44 | /// Add a query (prepared one) 45 | pub fn add_query_prepared(mut self, query: PreparedQuery, values: QueryValues) -> Self { 46 | self.queries.push(BatchQuery { 47 | is_prepared: true, 48 | subject: BatchQuerySubj::PreparedId(query), 49 | values, 50 | }); 51 | self 52 | } 53 | 54 | pub fn clear_queries(mut self) -> Self { 55 | self.queries = vec![]; 56 | self 57 | } 58 | 59 | pub fn consistency(mut self, consistency: Consistency) -> Self { 60 | self.consistency = consistency; 61 | self 62 | } 63 | 64 | pub fn serial_consistency(mut self, serial_consistency: Option) -> Self { 65 | self.serial_consistency = serial_consistency; 66 | self 67 | } 68 | 69 | pub fn timestamp(mut self, timestamp: Option) -> Self { 70 | self.timestamp = timestamp; 71 | self 72 | } 73 | 74 | pub fn finalize(self) -> CResult { 75 | let mut flags = vec![]; 76 | 77 | if self.serial_consistency.is_some() { 78 | flags.push(QueryFlags::WithSerialConsistency); 79 | } 80 | 81 | if self.timestamp.is_some() { 82 | flags.push(QueryFlags::WithDefaultTimestamp); 83 | } 84 | 85 | let with_names_for_values = self.queries.iter().all(|q| q.values.with_names()); 86 | 87 | if !with_names_for_values { 88 | let some_names_for_values = self.queries.iter().any(|q| q.values.with_names()); 89 | 90 | if some_names_for_values { 91 | return Err(CError::General(String::from( 92 | "Inconsistent query values - mixed \ 93 | with and without names values", 94 | ))); 95 | } 96 | } 97 | 98 | if with_names_for_values { 99 | flags.push(QueryFlags::WithNamesForValues); 100 | } 101 | 102 | Ok(BodyReqBatch { 103 | batch_type: self.batch_type, 104 | queries: self.queries, 105 | query_flags: flags, 106 | consistency: self.consistency, 107 | serial_consistency: self.serial_consistency, 108 | timestamp: self.timestamp, 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/query/exec_executor.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | 4 | use crate::cluster::{GetCompressor, GetConnection}; 5 | use crate::error; 6 | use crate::frame::{Frame, IntoBytes}; 7 | use crate::query::{QueryParams, QueryParamsBuilder, QueryValues, PreparedQuery, PrepareExecutor}; 8 | use crate::transport::CDRSTransport; 9 | 10 | use super::utils::{prepare_flags, send_frame}; 11 | use crate::error::Error; 12 | 13 | pub trait ExecExecutor< 14 | T: CDRSTransport + 'static, 15 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 16 | >: GetConnection + GetCompressor<'static> + PrepareExecutor 17 | { 18 | fn exec_with_params_tw( 19 | &self, 20 | prepared: &PreparedQuery, 21 | query_parameters: QueryParams, 22 | with_tracing: bool, 23 | with_warnings: bool, 24 | ) -> error::Result 25 | where 26 | Self: Sized, 27 | { 28 | let flags = prepare_flags(with_tracing, with_warnings); 29 | let options_frame = Frame::new_req_execute(&prepared.id.borrow(), &query_parameters, flags).into_cbytes(); 30 | 31 | let mut result = send_frame(self, options_frame); 32 | if let Err(Error::Server(error)) = &result { 33 | // if query is unprepared 34 | if error.error_code == 0x2500 { 35 | if let Ok(new) = self.prepare_raw(&prepared.query) { 36 | prepared.id.replace(new.id); 37 | let flags = prepare_flags(with_tracing, with_warnings); 38 | let options_frame = Frame::new_req_execute(&prepared.id.borrow(), &query_parameters, flags).into_cbytes(); 39 | result = send_frame(self, options_frame); 40 | } 41 | } 42 | } 43 | result 44 | } 45 | 46 | fn exec_with_params( 47 | &self, 48 | prepared: &PreparedQuery, 49 | query_parameters: QueryParams, 50 | ) -> error::Result 51 | where 52 | Self: Sized, 53 | { 54 | self.exec_with_params_tw(prepared, query_parameters, false, false) 55 | } 56 | 57 | fn exec_with_values_tw>( 58 | &self, 59 | prepared: &PreparedQuery, 60 | values: V, 61 | with_tracing: bool, 62 | with_warnings: bool, 63 | ) -> error::Result 64 | where 65 | Self: Sized, 66 | { 67 | let query_params_builder = QueryParamsBuilder::new(); 68 | let query_params = query_params_builder.values(values.into()).finalize(); 69 | self.exec_with_params_tw(prepared, query_params, with_tracing, with_warnings) 70 | } 71 | 72 | fn exec_with_values>( 73 | &self, 74 | prepared: &PreparedQuery, 75 | values: V, 76 | ) -> error::Result 77 | where 78 | Self: Sized, 79 | { 80 | self.exec_with_values_tw(prepared, values, false, false) 81 | } 82 | 83 | fn exec_tw( 84 | &self, 85 | prepared: &PreparedQuery, 86 | with_tracing: bool, 87 | with_warnings: bool, 88 | ) -> error::Result 89 | where 90 | Self: Sized, 91 | { 92 | let query_params = QueryParamsBuilder::new().finalize(); 93 | self.exec_with_params_tw(prepared, query_params, with_tracing, with_warnings) 94 | } 95 | 96 | fn exec(&mut self, prepared: &PreparedQuery) -> error::Result 97 | where 98 | Self: Sized, 99 | { 100 | self.exec_tw(prepared, false, false) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/query/mod.rs: -------------------------------------------------------------------------------- 1 | mod batch_executor; 2 | mod batch_query_builder; 3 | mod exec_executor; 4 | mod prepare_executor; 5 | mod prepared_query; 6 | mod query; 7 | mod query_executor; 8 | mod query_flags; 9 | mod query_params; 10 | mod query_params_builder; 11 | mod query_values; 12 | mod utils; 13 | 14 | pub use crate::query::batch_executor::BatchExecutor; 15 | pub use crate::query::batch_query_builder::{BatchQueryBuilder, QueryBatch}; 16 | pub use crate::query::exec_executor::ExecExecutor; 17 | pub use crate::query::prepare_executor::PrepareExecutor; 18 | pub use crate::query::prepared_query::PreparedQuery; 19 | pub use crate::query::query::Query; 20 | pub use crate::query::query_executor::QueryExecutor; 21 | pub use crate::query::query_flags::QueryFlags; 22 | pub use crate::query::query_params::QueryParams; 23 | pub use crate::query::query_params_builder::QueryParamsBuilder; 24 | pub use crate::query::query_values::QueryValues; 25 | -------------------------------------------------------------------------------- /src/query/prepare_executor.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | 4 | use crate::cluster::{GetCompressor, GetConnection}; 5 | use crate::error; 6 | use crate::frame::frame_result::BodyResResultPrepared; 7 | use crate::frame::{Frame, IntoBytes}; 8 | use crate::transport::CDRSTransport; 9 | 10 | use super::utils::{prepare_flags, send_frame}; 11 | use crate::query::PreparedQuery; 12 | 13 | pub trait PrepareExecutor< 14 | T: CDRSTransport + 'static, 15 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 16 | >: GetConnection + GetCompressor<'static> 17 | { 18 | /// It prepares a query for execution, along with query itself the 19 | /// method takes `with_tracing` and `with_warnings` flags to get 20 | /// tracing information and warnings. Return the raw prepared 21 | /// query result. 22 | fn prepare_raw_tw( 23 | &self, 24 | query: Q, 25 | with_tracing: bool, 26 | with_warnings: bool, 27 | ) -> error::Result 28 | where 29 | Self: Sized, 30 | { 31 | let flags = prepare_flags(with_tracing, with_warnings); 32 | 33 | let query_frame = Frame::new_req_prepare(query.to_string(), flags).into_cbytes(); 34 | 35 | send_frame(self, query_frame) 36 | .and_then(|response| response.get_body()) 37 | .and_then(|body| { 38 | Ok(body 39 | .into_prepared() 40 | .expect("CDRS BUG: cannot convert frame into prepared")) 41 | }) 42 | } 43 | 44 | /// It prepares query without additional tracing information and warnings. 45 | /// Return the raw prepared query result. 46 | fn prepare_raw(&self, query: Q) -> error::Result 47 | where 48 | Self: Sized, 49 | { 50 | self.prepare_raw_tw(query, false, false) 51 | } 52 | 53 | /// It prepares a query for execution, along with query itself 54 | /// the method takes `with_tracing` and `with_warnings` flags 55 | /// to get tracing information and warnings. Return the prepared 56 | /// query ID. 57 | fn prepare_tw( 58 | &self, 59 | query: Q, 60 | with_tracing: bool, 61 | with_warnings: bool, 62 | ) -> error::Result 63 | where 64 | Self: Sized, 65 | { 66 | let str = query.to_string(); 67 | self.prepare_raw_tw(query, with_tracing, with_warnings) 68 | .map(|x| PreparedQuery { id: RefCell::new(x.id), query: str }) 69 | } 70 | 71 | /// It prepares query without additional tracing information and warnings. 72 | /// Return the prepared query ID. 73 | fn prepare(&self, query: Q) -> error::Result 74 | where 75 | Self: Sized, 76 | { 77 | self.prepare_tw(query, false, false) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/query/prepared_query.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use crate::types::CBytesShort; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct PreparedQuery { 6 | pub(crate) id: RefCell, 7 | pub(crate) query: String, 8 | } 9 | -------------------------------------------------------------------------------- /src/query/query.rs: -------------------------------------------------------------------------------- 1 | use super::QueryParams; 2 | 3 | /// Structure that represents CQL query and parameters which will be applied during 4 | /// its execution 5 | #[derive(Debug, Default)] 6 | pub struct Query { 7 | pub query: String, 8 | pub params: QueryParams, 9 | } 10 | -------------------------------------------------------------------------------- /src/query/query_executor.rs: -------------------------------------------------------------------------------- 1 | use r2d2; 2 | use std::cell::RefCell; 3 | 4 | use crate::cluster::{GetCompressor, GetConnection}; 5 | use crate::error; 6 | use crate::frame::{Frame, IntoBytes}; 7 | use crate::query::{Query, QueryParams, QueryParamsBuilder, QueryValues}; 8 | use crate::transport::CDRSTransport; 9 | 10 | use super::utils::{prepare_flags, send_frame}; 11 | 12 | pub trait QueryExecutor< 13 | T: CDRSTransport + 'static, 14 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 15 | >: GetConnection + GetCompressor<'static> 16 | { 17 | fn query_with_params_tw( 18 | &self, 19 | query: Q, 20 | query_params: QueryParams, 21 | with_tracing: bool, 22 | with_warnings: bool, 23 | ) -> error::Result 24 | where 25 | Self: Sized, 26 | { 27 | let query = Query { 28 | query: query.to_string(), 29 | params: query_params, 30 | }; 31 | 32 | let flags = prepare_flags(with_tracing, with_warnings); 33 | 34 | let query_frame = Frame::new_query(query, flags).into_cbytes(); 35 | 36 | send_frame(self, query_frame) 37 | } 38 | 39 | /// Executes a query with default parameters: 40 | /// * TDB 41 | fn query(&self, query: Q) -> error::Result 42 | where 43 | Self: Sized, 44 | { 45 | self.query_tw(query, false, false) 46 | } 47 | 48 | /// Executes a query with ability to trace it and see warnings, and default parameters: 49 | /// * TBD 50 | fn query_tw( 51 | &self, 52 | query: Q, 53 | with_tracing: bool, 54 | with_warnings: bool, 55 | ) -> error::Result 56 | where 57 | Self: Sized, 58 | { 59 | let query_params = QueryParamsBuilder::new().finalize(); 60 | self.query_with_params_tw(query, query_params, with_tracing, with_warnings) 61 | } 62 | 63 | /// Executes a query with bounded values (either with or without names). 64 | fn query_with_values>( 65 | &self, 66 | query: Q, 67 | values: V, 68 | ) -> error::Result 69 | where 70 | Self: Sized, 71 | { 72 | self.query_with_values_tw(query, values, false, false) 73 | } 74 | 75 | /// Executes a query with bounded values (either with or without names) 76 | /// and ability to see warnings, trace a request and default parameters. 77 | fn query_with_values_tw>( 78 | &self, 79 | query: Q, 80 | values: V, 81 | with_tracing: bool, 82 | with_warnings: bool, 83 | ) -> error::Result 84 | where 85 | Self: Sized, 86 | { 87 | let query_params_builder = QueryParamsBuilder::new(); 88 | let query_params = query_params_builder.values(values.into()).finalize(); 89 | self.query_with_params_tw(query, query_params, with_tracing, with_warnings) 90 | } 91 | 92 | /// Executes a query with query params without warnings and tracing. 93 | fn query_with_params( 94 | &self, 95 | query: Q, 96 | query_params: QueryParams, 97 | ) -> error::Result 98 | where 99 | Self: Sized, 100 | { 101 | self.query_with_params_tw(query, query_params, false, false) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/query/query_params.rs: -------------------------------------------------------------------------------- 1 | use crate::consistency::Consistency; 2 | use crate::frame::AsByte; 3 | use crate::frame::IntoBytes; 4 | use crate::query::query_flags::QueryFlags; 5 | use crate::query::query_values::QueryValues; 6 | use crate::types::{to_bigint, to_int, to_short, CBytes}; 7 | 8 | /// Parameters of Query for query operation. 9 | #[derive(Debug, Default, Clone)] 10 | pub struct QueryParams { 11 | /// Cassandra consistency level. 12 | pub consistency: Consistency, 13 | /// Array of query flags. 14 | pub flags: Vec, 15 | /// Were values provided with names 16 | pub with_names: Option, 17 | /// Array of values. 18 | pub values: Option, 19 | /// Page size. 20 | pub page_size: Option, 21 | /// Array of bytes which represents paging state. 22 | pub paging_state: Option, 23 | /// Serial `Consistency`. 24 | pub serial_consistency: Option, 25 | /// Timestamp. 26 | pub timestamp: Option, 27 | } 28 | 29 | impl QueryParams { 30 | /// Sets values of Query request params. 31 | pub fn set_values(&mut self, values: QueryValues) { 32 | self.flags.push(QueryFlags::Value); 33 | self.values = Some(values); 34 | } 35 | 36 | fn flags_as_byte(&self) -> u8 { 37 | self.flags.iter().fold(0, |acc, flag| acc | flag.as_byte()) 38 | } 39 | 40 | #[allow(dead_code)] 41 | fn parse_query_flags(byte: u8) -> Vec { 42 | let mut flags: Vec = vec![]; 43 | 44 | if QueryFlags::has_value(byte) { 45 | flags.push(QueryFlags::Value); 46 | } 47 | if QueryFlags::has_skip_metadata(byte) { 48 | flags.push(QueryFlags::SkipMetadata); 49 | } 50 | if QueryFlags::has_page_size(byte) { 51 | flags.push(QueryFlags::PageSize); 52 | } 53 | if QueryFlags::has_with_paging_state(byte) { 54 | flags.push(QueryFlags::WithPagingState); 55 | } 56 | if QueryFlags::has_with_serial_consistency(byte) { 57 | flags.push(QueryFlags::WithSerialConsistency); 58 | } 59 | if QueryFlags::has_with_default_timestamp(byte) { 60 | flags.push(QueryFlags::WithDefaultTimestamp); 61 | } 62 | if QueryFlags::has_with_names_for_values(byte) { 63 | flags.push(QueryFlags::WithNamesForValues); 64 | } 65 | 66 | flags 67 | } 68 | } 69 | 70 | impl IntoBytes for QueryParams { 71 | fn into_cbytes(&self) -> Vec { 72 | let mut v: Vec = vec![]; 73 | 74 | v.extend_from_slice(self.consistency.into_cbytes().as_slice()); 75 | v.push(self.flags_as_byte()); 76 | if QueryFlags::has_value(self.flags_as_byte()) { 77 | if let Some(ref values) = self.values { 78 | v.extend_from_slice(to_short(values.len() as i16).as_slice()); 79 | v.extend_from_slice(values.into_cbytes().as_slice()); 80 | } 81 | } 82 | if QueryFlags::has_page_size(self.flags_as_byte()) && self.page_size.is_some() { 83 | // XXX clone 84 | v.extend_from_slice( 85 | to_int( 86 | self.page_size 87 | .clone() 88 | // unwrap is safe as we've checked that 89 | // self.page_size.is_some() 90 | .unwrap(), 91 | ) 92 | .as_slice(), 93 | ); 94 | } 95 | if QueryFlags::has_with_paging_state(self.flags_as_byte()) && self.paging_state.is_some() { 96 | // XXX clone 97 | v.extend_from_slice( 98 | self.paging_state 99 | .clone() 100 | // unwrap is safe as we've checked that 101 | // self.paging_state.is_some() 102 | .unwrap() 103 | .into_cbytes() 104 | .as_slice(), 105 | ); 106 | } 107 | if QueryFlags::has_with_serial_consistency(self.flags_as_byte()) 108 | && self.serial_consistency.is_some() 109 | { 110 | // XXX clone 111 | v.extend_from_slice( 112 | self.serial_consistency 113 | .clone() 114 | // unwrap is safe as we've checked that 115 | // self.serial_consistency.is_some() 116 | .unwrap() 117 | .into_cbytes() 118 | .as_slice(), 119 | ); 120 | } 121 | if QueryFlags::has_with_default_timestamp(self.flags_as_byte()) && self.timestamp.is_some() 122 | { 123 | // unwrap is safe as we've checked that self.timestamp.is_some() 124 | v.extend_from_slice(to_bigint(self.timestamp.unwrap()).as_slice()); 125 | } 126 | 127 | v 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/query/query_params_builder.rs: -------------------------------------------------------------------------------- 1 | use super::{QueryFlags, QueryParams, QueryValues}; 2 | use crate::consistency::Consistency; 3 | use crate::types::CBytes; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct QueryParamsBuilder { 7 | consistency: Consistency, 8 | flags: Option>, 9 | values: Option, 10 | with_names: Option, 11 | page_size: Option, 12 | paging_state: Option, 13 | serial_consistency: Option, 14 | timestamp: Option, 15 | } 16 | 17 | impl QueryParamsBuilder { 18 | /// Factory function that returns new `QueryBuilder`. 19 | /// Default consistency level is `One` 20 | pub fn new() -> QueryParamsBuilder { 21 | Default::default() 22 | } 23 | 24 | /// Sets new query consistency 25 | pub fn consistency(mut self, consistency: Consistency) -> Self { 26 | self.consistency = consistency; 27 | 28 | self 29 | } 30 | 31 | /// Sets new flags. 32 | builder_opt_field!(flags, Vec); 33 | 34 | /// Sets new values. 35 | /// Sets new query consistency 36 | pub fn values(mut self, values: QueryValues) -> Self { 37 | let with_names = values.with_names(); 38 | self.with_names = Some(with_names); 39 | self.values = Some(values); 40 | self.flags = self.flags.or(Some(vec![])).map(|mut flags| { 41 | flags.push(QueryFlags::Value); 42 | if with_names { 43 | flags.push(QueryFlags::WithNamesForValues); 44 | } 45 | flags 46 | }); 47 | 48 | self 49 | } 50 | 51 | /// Sets new with_names parameter value. 52 | builder_opt_field!(with_names, bool); 53 | 54 | /// Sets new values. 55 | /// Sets new query consistency 56 | pub fn page_size(mut self, size: i32) -> Self { 57 | self.page_size = Some(size); 58 | self.flags = self.flags.or(Some(vec![])).map(|mut flags| { 59 | flags.push(QueryFlags::PageSize); 60 | flags 61 | }); 62 | 63 | self 64 | } 65 | 66 | /// Sets new values. 67 | /// Sets new query consistency 68 | pub fn paging_state(mut self, state: CBytes) -> Self { 69 | self.paging_state = Some(state); 70 | self.flags = self.flags.or(Some(vec![])).map(|mut flags| { 71 | flags.push(QueryFlags::WithPagingState); 72 | flags 73 | }); 74 | 75 | self 76 | } 77 | 78 | /// Sets new serial_consistency value. 79 | builder_opt_field!(serial_consistency, Consistency); 80 | 81 | /// Sets new timestamp value. 82 | builder_opt_field!(timestamp, i64); 83 | 84 | /// Finalizes query building process and returns query itself 85 | pub fn finalize(self) -> QueryParams { 86 | QueryParams { 87 | consistency: self.consistency, 88 | flags: self.flags.unwrap_or(vec![]), 89 | values: self.values, 90 | with_names: self.with_names, 91 | page_size: self.page_size, 92 | paging_state: self.paging_state, 93 | serial_consistency: self.serial_consistency, 94 | timestamp: self.timestamp, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/query/query_values.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::Hash; 3 | 4 | use crate::frame::IntoBytes; 5 | use crate::types::value::Value; 6 | use crate::types::CString; 7 | 8 | /// Enum that represents two types of query values: 9 | /// * values without name 10 | /// * values with names 11 | #[derive(Debug, Clone, PartialEq)] 12 | pub enum QueryValues { 13 | SimpleValues(Vec), 14 | NamedValues(HashMap), 15 | } 16 | 17 | impl QueryValues { 18 | /// It returns `true` if query values is with names and `false` otherwise. 19 | pub fn with_names(&self) -> bool { 20 | match *self { 21 | QueryValues::SimpleValues(_) => false, 22 | _ => true, 23 | } 24 | } 25 | 26 | /// It return number of values. 27 | pub fn len(&self) -> usize { 28 | match *self { 29 | QueryValues::SimpleValues(ref v) => v.len(), 30 | QueryValues::NamedValues(ref m) => m.len(), 31 | } 32 | } 33 | 34 | fn named_value_into_bytes_fold(mut bytes: Vec, vals: (&String, &Value)) -> Vec { 35 | let mut name_bytes = CString::new(vals.0.clone()).into_cbytes(); 36 | let mut vals_bytes = vals.1.into_cbytes(); 37 | bytes.append(&mut name_bytes); 38 | bytes.append(&mut vals_bytes); 39 | bytes 40 | } 41 | 42 | fn value_into_bytes_fold(mut bytes: Vec, val: &Value) -> Vec { 43 | let mut val_bytes = val.into_cbytes(); 44 | bytes.append(&mut val_bytes); 45 | bytes 46 | } 47 | } 48 | 49 | impl + Clone> From> for QueryValues { 50 | /// It converts values from `Vec` to query values without names `QueryValues::SimpleValues`. 51 | fn from(values: Vec) -> QueryValues { 52 | let vals = values.iter().map(|v| v.clone().into()); 53 | QueryValues::SimpleValues(vals.collect()) 54 | } 55 | } 56 | 57 | impl<'a, T: Into + Clone> From<&'a [T]> for QueryValues { 58 | /// It converts values from `Vec` to query values without names `QueryValues::SimpleValues`. 59 | fn from(values: &'a [T]) -> QueryValues { 60 | let vals = values.iter().map(|v| v.clone().into()); 61 | QueryValues::SimpleValues(vals.collect()) 62 | } 63 | } 64 | 65 | impl + Clone> From> for QueryValues { 66 | /// It converts values from `HashMap` to query values with names `QueryValues::NamedValues`. 67 | fn from(values: HashMap) -> QueryValues { 68 | let map: HashMap = HashMap::with_capacity(values.len()); 69 | let _values = values.iter().fold(map, |mut acc, v| { 70 | let name = v.0; 71 | let val = v.1; 72 | acc.insert(name.to_string(), val.clone().into()); 73 | acc 74 | }); 75 | QueryValues::NamedValues(_values) 76 | } 77 | } 78 | 79 | impl IntoBytes for QueryValues { 80 | fn into_cbytes(&self) -> Vec { 81 | let bytes: Vec = vec![]; 82 | match *self { 83 | QueryValues::SimpleValues(ref v) => { 84 | v.iter().fold(bytes, QueryValues::value_into_bytes_fold) 85 | } 86 | QueryValues::NamedValues(ref v) => v 87 | .iter() 88 | .fold(bytes, QueryValues::named_value_into_bytes_fold), 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/query/utils.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use crate::cluster::{GetCompressor, GetConnection}; 4 | use crate::error; 5 | use crate::frame::parser::from_connection; 6 | use crate::frame::{Flag, Frame}; 7 | use crate::transport::CDRSTransport; 8 | 9 | pub fn prepare_flags(with_tracing: bool, with_warnings: bool) -> Vec { 10 | let mut flags = vec![]; 11 | 12 | if with_tracing { 13 | flags.push(Flag::Tracing); 14 | } 15 | 16 | if with_warnings { 17 | flags.push(Flag::Warning); 18 | } 19 | 20 | flags 21 | } 22 | 23 | pub fn send_frame(sender: &S, frame_bytes: Vec) -> error::Result 24 | where 25 | S: GetConnection + GetCompressor<'static> + Sized, 26 | T: CDRSTransport + 'static, 27 | M: r2d2::ManageConnection, Error = error::Error> + Sized, 28 | { 29 | let ref compression = sender.get_compressor(); 30 | let transport_cell = sender 31 | .get_connection() 32 | .ok_or(error::Error::from("Unable to get transport"))?; 33 | 34 | transport_cell 35 | .borrow_mut() 36 | .write_all(frame_bytes.as_slice()) 37 | .map_err(error::Error::from)?; 38 | 39 | from_connection(&transport_cell, compression) 40 | } 41 | 42 | #[cfg(test)] 43 | mod test { 44 | use super::*; 45 | 46 | #[test] 47 | fn prepare_flags_test() { 48 | assert_eq!(prepare_flags(true, false), vec![Flag::Tracing]); 49 | assert_eq!(prepare_flags(false, true), vec![Flag::Warning]); 50 | assert_eq!( 51 | prepare_flags(true, true), 52 | vec![Flag::Tracing, Flag::Warning] 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! fixture { 3 | ($fxt:expr) => { 4 | { 5 | use std::fs::File; 6 | use std::path::Path; 7 | use std::io::Read; 8 | File::open(Path::new("./fixtures").join($fxt).as_path()).map(|mut f| { 9 | let mut bytes = vec![]; 10 | f.read_to_end(&mut bytes).unwrap(); 11 | bytes 12 | }) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/types/blob.rs: -------------------------------------------------------------------------------- 1 | /// Special type that represents Cassandra blob type. 2 | #[derive(PartialEq, Eq, Hash, Debug, Clone)] 3 | pub struct Blob(Vec); 4 | 5 | impl Blob { 6 | /// Constructor method that creates new blob value from a vector of bytes. 7 | pub fn new(bytes: Vec) -> Self { 8 | Blob(bytes) 9 | } 10 | 11 | /// Returns a mutable reference to an underlying slice of bytes. 12 | pub fn as_mut_slice<'a>(&'a mut self) -> &'a [u8] { 13 | self.0.as_mut_slice() 14 | } 15 | 16 | /// Returns underlying vector of bytes. 17 | pub fn into_vec(self) -> Vec { 18 | self.0 19 | } 20 | } 21 | 22 | impl From> for Blob { 23 | fn from(vec: Vec) -> Self { 24 | Blob::new(vec) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/types/decimal.rs: -------------------------------------------------------------------------------- 1 | use super::{to_int, to_varint}; 2 | use crate::frame::traits::IntoBytes; 3 | 4 | /// Cassandra Decimal type 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct Decimal { 7 | pub unscaled: i64, 8 | pub scale: u32, 9 | } 10 | 11 | impl Decimal { 12 | pub fn new(unscaled: i64, scale: u32) -> Self { 13 | Decimal { unscaled, scale } 14 | } 15 | 16 | /// Method that returns plain `f64` value. 17 | pub fn as_plain(&self) -> f64 { 18 | (self.unscaled as f64) / (10i64.pow(self.scale) as f64) 19 | } 20 | } 21 | 22 | impl IntoBytes for Decimal { 23 | fn into_cbytes(&self) -> Vec { 24 | let mut bytes: Vec = vec![]; 25 | bytes.extend(to_int(self.scale as i32)); 26 | bytes.extend(to_varint(self.unscaled)); 27 | 28 | bytes 29 | } 30 | } 31 | 32 | macro_rules! impl_from_for_decimal { 33 | ($t:ty) => { 34 | impl From<$t> for Decimal { 35 | fn from(i: $t) -> Self { 36 | Decimal { 37 | unscaled: i as i64, 38 | scale: 0, 39 | } 40 | } 41 | } 42 | }; 43 | } 44 | 45 | impl_from_for_decimal!(i8); 46 | impl_from_for_decimal!(i16); 47 | impl_from_for_decimal!(i32); 48 | impl_from_for_decimal!(i64); 49 | impl_from_for_decimal!(u8); 50 | impl_from_for_decimal!(u16); 51 | 52 | impl From for Decimal { 53 | fn from(f: f32) -> Decimal { 54 | let mut scale: u32 = 0; 55 | 56 | loop { 57 | let unscaled = f * (10i64.pow(scale) as f32); 58 | 59 | if unscaled == unscaled.trunc() { 60 | return Decimal::new(unscaled as i64, scale); 61 | } 62 | 63 | scale += 1; 64 | } 65 | } 66 | } 67 | 68 | impl From for Decimal { 69 | fn from(f: f64) -> Decimal { 70 | let mut scale: u32 = 0; 71 | 72 | loop { 73 | let unscaled = f * (10i64.pow(scale) as f64); 74 | 75 | if unscaled == unscaled.trunc() { 76 | return Decimal::new(unscaled as i64, scale); 77 | } 78 | 79 | scale += 1; 80 | } 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod test { 86 | use super::*; 87 | 88 | #[test] 89 | fn into_cbytes_test() { 90 | assert_eq!( 91 | Decimal::new(129, 0).into_cbytes(), 92 | vec![0, 0, 0, 0, 0x00, 0x81] 93 | ); 94 | 95 | assert_eq!( 96 | Decimal::new(-129, 0).into_cbytes(), 97 | vec![0, 0, 0, 0, 0xFF, 0x7F] 98 | ); 99 | 100 | let expected: Vec = vec![0, 0, 0, 1, 0x00, 0x81]; 101 | assert_eq!(Decimal::new(129, 1).into_cbytes(), expected); 102 | 103 | let expected: Vec = vec![0, 0, 0, 1, 0xFF, 0x7F]; 104 | assert_eq!(Decimal::new(-129, 1).into_cbytes(), expected); 105 | } 106 | 107 | #[test] 108 | fn from_f32() { 109 | assert_eq!(Decimal::from(12300001 as f32), Decimal::new(12300001, 0)); 110 | assert_eq!(Decimal::from(1230000.1 as f32), Decimal::new(12300001, 1)); 111 | assert_eq!(Decimal::from(0.12300001 as f32), Decimal::new(12300001, 8)); 112 | } 113 | 114 | #[test] 115 | fn from_f64() { 116 | assert_eq!( 117 | Decimal::from(1230000000000001i64 as f64), 118 | Decimal::new(1230000000000001i64, 0) 119 | ); 120 | assert_eq!( 121 | Decimal::from(123000000000000.1f64), 122 | Decimal::new(1230000000000001i64, 1) 123 | ); 124 | assert_eq!( 125 | Decimal::from(0.1230000000000001f64), 126 | Decimal::new(1230000000000001i64, 16) 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/types/from_cdrs.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use time::PrimitiveDateTime; 3 | use uuid::Uuid; 4 | 5 | use crate::error::Result as CDRSResult; 6 | use crate::types::blob::Blob; 7 | use crate::types::decimal::Decimal; 8 | use crate::types::list::List; 9 | use crate::types::map::Map; 10 | use crate::types::tuple::Tuple; 11 | use crate::types::udt::UDT; 12 | use crate::types::{AsRustType, ByName, IntoRustByName}; 13 | 14 | pub trait FromCDRS { 15 | fn from_cdrs(cdrs_type: T) -> CDRSResult> 16 | where 17 | Self: Sized, 18 | T: AsRustType + Sized, 19 | { 20 | cdrs_type.as_rust_type() 21 | } 22 | 23 | fn from_cdrs_r(cdrs_type: T) -> CDRSResult 24 | where 25 | Self: Sized, 26 | T: AsRustType + Sized, 27 | { 28 | cdrs_type.as_r_type() 29 | } 30 | } 31 | 32 | impl FromCDRS for Blob {} 33 | impl FromCDRS for String {} 34 | impl FromCDRS for bool {} 35 | impl FromCDRS for i64 {} 36 | impl FromCDRS for i32 {} 37 | impl FromCDRS for i16 {} 38 | impl FromCDRS for i8 {} 39 | impl FromCDRS for f64 {} 40 | impl FromCDRS for f32 {} 41 | impl FromCDRS for IpAddr {} 42 | impl FromCDRS for Uuid {} 43 | impl FromCDRS for List {} 44 | impl FromCDRS for Map {} 45 | impl FromCDRS for UDT {} 46 | impl FromCDRS for Tuple {} 47 | impl FromCDRS for PrimitiveDateTime {} 48 | impl FromCDRS for Decimal {} 49 | 50 | pub trait FromCDRSByName { 51 | fn from_cdrs_by_name(cdrs_type: &T, name: &str) -> CDRSResult> 52 | where 53 | Self: Sized, 54 | T: ByName + IntoRustByName + Sized, 55 | { 56 | cdrs_type.by_name(name) 57 | } 58 | 59 | fn from_cdrs_r(cdrs_type: &T, name: &str) -> CDRSResult 60 | where 61 | Self: Sized, 62 | T: ByName + IntoRustByName + Sized + ::std::fmt::Debug, 63 | { 64 | cdrs_type.r_by_name(name) 65 | } 66 | } 67 | 68 | impl FromCDRSByName for Blob {} 69 | impl FromCDRSByName for String {} 70 | impl FromCDRSByName for bool {} 71 | impl FromCDRSByName for i64 {} 72 | impl FromCDRSByName for i32 {} 73 | impl FromCDRSByName for i16 {} 74 | impl FromCDRSByName for i8 {} 75 | impl FromCDRSByName for f64 {} 76 | impl FromCDRSByName for f32 {} 77 | impl FromCDRSByName for IpAddr {} 78 | impl FromCDRSByName for Uuid {} 79 | impl FromCDRSByName for List {} 80 | impl FromCDRSByName for Map {} 81 | impl FromCDRSByName for UDT {} 82 | impl FromCDRSByName for Tuple {} 83 | impl FromCDRSByName for PrimitiveDateTime {} 84 | impl FromCDRSByName for Decimal {} 85 | -------------------------------------------------------------------------------- /src/types/list.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::frame::frame_result::{ColType, ColTypeOption, ColTypeOptionValue}; 3 | use crate::types::blob::Blob; 4 | use crate::types::data_serialization_types::*; 5 | use crate::types::decimal::Decimal; 6 | use crate::types::map::Map; 7 | use crate::types::tuple::Tuple; 8 | use crate::types::udt::UDT; 9 | use crate::types::{AsRust, AsRustType, CBytes}; 10 | use std::net::IpAddr; 11 | use uuid::Uuid; 12 | 13 | // TODO: consider using pointers to ColTypeOption and Vec instead of owning them. 14 | #[derive(Debug)] 15 | pub struct List { 16 | /// column spec of the list, i.e. id should be List as it's a list and value should contain 17 | /// a type of list items. 18 | metadata: ColTypeOption, 19 | data: Vec, 20 | } 21 | 22 | impl List { 23 | pub fn new(data: Vec, metadata: ColTypeOption) -> List { 24 | List { 25 | metadata: metadata, 26 | data: data, 27 | } 28 | } 29 | 30 | fn map(&self, f: F) -> Vec 31 | where 32 | F: FnMut(&CBytes) -> T, 33 | { 34 | self.data.iter().map(f).collect() 35 | } 36 | } 37 | 38 | impl AsRust for List {} 39 | 40 | list_as_rust!(Blob); 41 | list_as_rust!(String); 42 | list_as_rust!(bool); 43 | list_as_rust!(i64); 44 | list_as_rust!(i32); 45 | list_as_rust!(i16); 46 | list_as_rust!(i8); 47 | list_as_rust!(f64); 48 | list_as_rust!(f32); 49 | list_as_rust!(IpAddr); 50 | list_as_rust!(Uuid); 51 | list_as_rust!(List); 52 | list_as_rust!(Map); 53 | list_as_rust!(UDT); 54 | list_as_rust!(Tuple); 55 | list_as_rust!(Decimal); 56 | -------------------------------------------------------------------------------- /src/types/rows.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use time::PrimitiveDateTime; 3 | use uuid::Uuid; 4 | 5 | use crate::error::{column_is_empty_err, Error, Result}; 6 | use crate::frame::frame_result::{ 7 | BodyResResultRows, ColSpec, ColType, ColTypeOption, ColTypeOptionValue, RowsMetadata, 8 | }; 9 | use crate::types::blob::Blob; 10 | use crate::types::data_serialization_types::*; 11 | use crate::types::decimal::Decimal; 12 | use crate::types::list::List; 13 | use crate::types::map::Map; 14 | use crate::types::tuple::Tuple; 15 | use crate::types::udt::UDT; 16 | use crate::types::{ByIndex, ByName, CBytes, IntoRustByIndex, IntoRustByName}; 17 | 18 | #[derive(Clone, Debug)] 19 | pub struct Row { 20 | metadata: RowsMetadata, 21 | row_content: Vec, 22 | } 23 | 24 | impl Row { 25 | pub fn from_frame_body(body: BodyResResultRows) -> Vec { 26 | body.rows_content 27 | .iter() 28 | .map(|row| Row { 29 | metadata: body.metadata.clone(), 30 | row_content: row.clone(), 31 | }) 32 | .collect() 33 | } 34 | 35 | fn get_col_spec_by_name(&self, name: &str) -> Option<(&ColSpec, &CBytes)> { 36 | self.metadata 37 | .col_specs 38 | .iter() 39 | .position(|spec| spec.name.as_str() == name) 40 | .map(|i| { 41 | let ref col_spec = self.metadata.col_specs[i]; 42 | let ref data = self.row_content[i]; 43 | (col_spec, data) 44 | }) 45 | } 46 | 47 | fn get_col_spec_by_index(&self, index: usize) -> Option<(&ColSpec, &CBytes)> { 48 | let specs = self.metadata.col_specs.iter(); 49 | let values = self.row_content.iter(); 50 | specs.zip(values).nth(index) 51 | } 52 | } 53 | 54 | impl ByName for Row {} 55 | 56 | into_rust_by_name!(Row, Blob); 57 | into_rust_by_name!(Row, String); 58 | into_rust_by_name!(Row, bool); 59 | into_rust_by_name!(Row, i64); 60 | into_rust_by_name!(Row, i32); 61 | into_rust_by_name!(Row, i16); 62 | into_rust_by_name!(Row, i8); 63 | into_rust_by_name!(Row, f64); 64 | into_rust_by_name!(Row, f32); 65 | into_rust_by_name!(Row, IpAddr); 66 | into_rust_by_name!(Row, Uuid); 67 | into_rust_by_name!(Row, List); 68 | into_rust_by_name!(Row, Map); 69 | into_rust_by_name!(Row, UDT); 70 | into_rust_by_name!(Row, Tuple); 71 | into_rust_by_name!(Row, PrimitiveDateTime); 72 | into_rust_by_name!(Row, Decimal); 73 | 74 | impl ByIndex for Row {} 75 | 76 | into_rust_by_index!(Row, Blob); 77 | into_rust_by_index!(Row, String); 78 | into_rust_by_index!(Row, bool); 79 | into_rust_by_index!(Row, i64); 80 | into_rust_by_index!(Row, i32); 81 | into_rust_by_index!(Row, i16); 82 | into_rust_by_index!(Row, i8); 83 | into_rust_by_index!(Row, f64); 84 | into_rust_by_index!(Row, f32); 85 | into_rust_by_index!(Row, IpAddr); 86 | into_rust_by_index!(Row, Uuid); 87 | into_rust_by_index!(Row, List); 88 | into_rust_by_index!(Row, Map); 89 | into_rust_by_index!(Row, UDT); 90 | into_rust_by_index!(Row, Tuple); 91 | into_rust_by_index!(Row, PrimitiveDateTime); 92 | into_rust_by_index!(Row, Decimal); 93 | -------------------------------------------------------------------------------- /src/types/tuple.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use time::PrimitiveDateTime; 3 | use uuid::Uuid; 4 | 5 | use crate::error::{column_is_empty_err, Error, Result}; 6 | use crate::frame::frame_result::{CTuple, ColType, ColTypeOption, ColTypeOptionValue}; 7 | use crate::types::blob::Blob; 8 | use crate::types::data_serialization_types::*; 9 | use crate::types::decimal::Decimal; 10 | use crate::types::list::List; 11 | use crate::types::map::Map; 12 | use crate::types::udt::UDT; 13 | use crate::types::{ByIndex, CBytes, IntoRustByIndex}; 14 | 15 | use std::hash::{Hash, Hasher}; 16 | 17 | #[derive(Debug)] 18 | pub struct Tuple { 19 | data: Vec<(ColTypeOption, CBytes)>, 20 | } 21 | 22 | impl PartialEq for Tuple { 23 | fn eq(&self, other: &Tuple) -> bool { 24 | if self.data.len() != other.data.len() { 25 | return false; 26 | } 27 | for (s, o) in self.data.iter().zip(other.data.iter()) { 28 | if s.1 != o.1 { 29 | return false; 30 | } 31 | } 32 | true 33 | } 34 | } 35 | 36 | impl Eq for Tuple {} 37 | 38 | impl Hash for Tuple { 39 | fn hash(&self, state: &mut H) { 40 | for data in &self.data { 41 | data.1.hash(state); 42 | } 43 | } 44 | } 45 | 46 | impl Tuple { 47 | pub fn new<'a>(data: Vec, metadata: &'a CTuple) -> Tuple { 48 | let meta_iter = metadata.types.iter(); 49 | 50 | let acc = Vec::with_capacity(metadata.types.len()); 51 | let d = meta_iter.zip(data.iter()).fold(acc, |mut a, v| { 52 | let (val_type, val_b) = v; 53 | a.push((val_type.clone(), val_b.clone())); 54 | a 55 | }); 56 | 57 | Tuple { data: d } 58 | } 59 | } 60 | 61 | impl ByIndex for Tuple {} 62 | 63 | into_rust_by_index!(Tuple, Blob); 64 | into_rust_by_index!(Tuple, String); 65 | into_rust_by_index!(Tuple, bool); 66 | into_rust_by_index!(Tuple, i64); 67 | into_rust_by_index!(Tuple, i32); 68 | into_rust_by_index!(Tuple, i16); 69 | into_rust_by_index!(Tuple, i8); 70 | into_rust_by_index!(Tuple, f64); 71 | into_rust_by_index!(Tuple, f32); 72 | into_rust_by_index!(Tuple, IpAddr); 73 | into_rust_by_index!(Tuple, Uuid); 74 | into_rust_by_index!(Tuple, List); 75 | into_rust_by_index!(Tuple, Map); 76 | into_rust_by_index!(Tuple, UDT); 77 | into_rust_by_index!(Tuple, Tuple); 78 | into_rust_by_index!(Tuple, PrimitiveDateTime); 79 | into_rust_by_index!(Tuple, Decimal); 80 | -------------------------------------------------------------------------------- /src/types/udt.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::IpAddr; 3 | use time::PrimitiveDateTime; 4 | use uuid::Uuid; 5 | 6 | use crate::error::{column_is_empty_err, Error, Result}; 7 | use crate::frame::frame_result::{CUdt, ColType, ColTypeOption, ColTypeOptionValue}; 8 | use crate::types::blob::Blob; 9 | use crate::types::data_serialization_types::*; 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::{ByName, CBytes, IntoRustByName}; 15 | 16 | #[derive(Clone, Debug)] 17 | pub struct UDT { 18 | data: HashMap, 19 | } 20 | 21 | impl UDT { 22 | pub fn new<'a>(data: Vec, metadata: &'a CUdt) -> UDT { 23 | let meta_iter = metadata.descriptions.iter(); 24 | 25 | let acc: HashMap = 26 | HashMap::with_capacity(metadata.descriptions.len()); 27 | let d = meta_iter.zip(data.iter()).fold(acc, |mut a, v| { 28 | let (m, val_b) = v; 29 | let &(ref name_b, ref val_type) = m; 30 | let name = name_b.as_plain(); 31 | a.insert(name, (val_type.clone(), val_b.clone())); 32 | a 33 | }); 34 | 35 | UDT { data: d } 36 | } 37 | } 38 | 39 | impl ByName for UDT {} 40 | 41 | into_rust_by_name!(UDT, Blob); 42 | into_rust_by_name!(UDT, String); 43 | into_rust_by_name!(UDT, bool); 44 | into_rust_by_name!(UDT, i64); 45 | into_rust_by_name!(UDT, i32); 46 | into_rust_by_name!(UDT, i16); 47 | into_rust_by_name!(UDT, i8); 48 | into_rust_by_name!(UDT, f64); 49 | into_rust_by_name!(UDT, f32); 50 | into_rust_by_name!(UDT, IpAddr); 51 | into_rust_by_name!(UDT, Uuid); 52 | into_rust_by_name!(UDT, List); 53 | into_rust_by_name!(UDT, Map); 54 | into_rust_by_name!(UDT, UDT); 55 | into_rust_by_name!(UDT, Tuple); 56 | into_rust_by_name!(UDT, PrimitiveDateTime); 57 | into_rust_by_name!(UDT, Decimal); 58 | -------------------------------------------------------------------------------- /tests/build-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The script builds Apache Cassandra two-node cluster 4 | # The nodes could be reached by 127.0.0.1:9042 and 127.0.0.1:9043 address 5 | docker stop cass1 6 | docker rm cass1 7 | docker run -d -p 9042:9042 --name cass1 cassandra:3.9 8 | 9 | docker stop cass2 10 | docker rm cass2 11 | docker run -d -p 9043:9042 --name cass2 \ 12 | -e CASSANDRA_SEEDS="$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' cass1)" \ 13 | cassandra:3.9 14 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "e2e-tests")] 2 | use cdrs::authenticators::NoneAuthenticator; 3 | #[cfg(feature = "e2e-tests")] 4 | use cdrs::cluster::session::{new as new_session, Session}; 5 | #[cfg(feature = "e2e-tests")] 6 | use cdrs::cluster::{ClusterTcpConfig, NodeTcpConfigBuilder, TcpConnectionPool}; 7 | #[cfg(feature = "e2e-tests")] 8 | use cdrs::error::Result; 9 | #[cfg(feature = "e2e-tests")] 10 | use cdrs::load_balancing::RoundRobin; 11 | #[cfg(feature = "e2e-tests")] 12 | use cdrs::query::QueryExecutor; 13 | #[cfg(feature = "e2e-tests")] 14 | use regex::Regex; 15 | 16 | #[cfg(feature = "e2e-tests")] 17 | const ADDR: &'static str = "localhost:9042"; 18 | 19 | #[cfg(feature = "e2e-tests")] 20 | type CurrentSession = Session>>; 21 | 22 | #[cfg(feature = "e2e-tests")] 23 | pub fn setup(create_table_cql: &'static str) -> Result { 24 | setup_multiple(&[create_table_cql]) 25 | } 26 | 27 | #[cfg(feature = "e2e-tests")] 28 | pub fn setup_multiple(create_cqls: &[&'static str]) -> Result { 29 | let node = NodeTcpConfigBuilder::new(ADDR, NoneAuthenticator {}).build(); 30 | let cluster_config = ClusterTcpConfig(vec![node]); 31 | let lb = RoundRobin::new(); 32 | let session = new_session(&cluster_config, lb).expect("session should be created"); 33 | let re_table_name = Regex::new(r"CREATE TABLE IF NOT EXISTS (\w+\.\w+)").unwrap(); 34 | 35 | let create_keyspace_query = "CREATE KEYSPACE IF NOT EXISTS cdrs_test WITH \ 36 | replication = {'class': 'SimpleStrategy', 'replication_factor': 1} \ 37 | AND durable_writes = false"; 38 | session.query(create_keyspace_query)?; 39 | 40 | for create_cql in create_cqls.iter() { 41 | let table_name = re_table_name 42 | .captures(create_cql) 43 | .map(|cap| cap.get(1).unwrap().as_str()); 44 | 45 | // Re-using tables is a lot faster than creating/dropping them for every test. 46 | // But if table definitions change while editing tests 47 | // the old tables need to be dropped. For example by uncommenting the following lines. 48 | // if let Some(table_name) = table_name { 49 | // let cql = format!("DROP TABLE IF EXISTS {}", table_name); 50 | // let query = QueryBuilder::new(cql).finalize(); 51 | // session.query(query, true, true)?; 52 | // } 53 | 54 | session.query(create_cql.to_owned())?; 55 | 56 | if let Some(table_name) = table_name { 57 | let cql = format!("TRUNCATE TABLE {}", table_name); 58 | session.query(cql)?; 59 | } 60 | } 61 | 62 | Ok(session) 63 | } 64 | -------------------------------------------------------------------------------- /tests/query_values.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "e2e-tests")] 2 | #[macro_use] 3 | extern crate cdrs; 4 | 5 | mod common; 6 | 7 | #[cfg(feature = "e2e-tests")] 8 | use common::*; 9 | 10 | #[cfg(feature = "e2e-tests")] 11 | use cdrs::query::QueryExecutor; 12 | #[cfg(feature = "e2e-tests")] 13 | use cdrs::types::{AsRust, ByName, IntoRustByName}; 14 | 15 | #[cfg(feature = "e2e-tests")] 16 | use std::str::FromStr; 17 | 18 | #[test] 19 | #[cfg(feature = "e2e-tests")] 20 | fn query_values_in() { 21 | let cql = "CREATE TABLE IF NOT EXISTS cdrs_test.test_query_values_in \ 22 | (id text PRIMARY KEY)"; 23 | let session = setup(cql).expect("setup"); 24 | 25 | session.query(cql).expect("create table error"); 26 | 27 | let query_insert = "INSERT INTO cdrs_test.test_query_values_in \ 28 | (id) VALUES (?)"; 29 | 30 | let items = vec!["1".to_string(), "2".to_string(), "3".to_string()]; 31 | 32 | for item in items { 33 | let values = query_values!(item); 34 | session 35 | .query_with_values(query_insert, values) 36 | .expect("insert item error"); 37 | } 38 | 39 | let cql = "SELECT * FROM cdrs_test.test_query_values_in WHERE id IN ?"; 40 | let criteria = vec!["1".to_string(), "3".to_string()]; 41 | 42 | let rows = session 43 | .query_with_values(cql, query_values!(criteria.clone())) 44 | .expect("select values query error") 45 | .get_body() 46 | .expect("get body error") 47 | .into_rows() 48 | .expect("converting into rows error"); 49 | 50 | assert_eq!(rows.len(), criteria.len()); 51 | 52 | let found_all_matching_criteria = criteria.iter().all(|criteria_item: &String| { 53 | rows.iter().any(|row| { 54 | let id: String = row.get_r_by_name("id").expect("id"); 55 | 56 | criteria_item.clone() == id 57 | }) 58 | }); 59 | 60 | assert!( 61 | found_all_matching_criteria, 62 | "should find at least one element for each criteria" 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /tests/scylla-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run -d -p 9042:9042 --name scylladb scylladb/scylla 4 | # docker run --name some-scylla -d scylladb/scylla 5 | -------------------------------------------------------------------------------- /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` [example](https://github.com/AlexPikalov/cdrs/blob/master/examples/all.rs#L159) | 27 | | set | `List -> Vec` [example](https://github.com/AlexPikalov/cdrs/blob/master/examples/all.rs#L159)| 28 | | map | `Map -> HashMap` [example](https://github.com/AlexPikalov/cdrs/blob/master/examples/all.rs#L185) | 29 | | udt | Rust struct + custom [implementation into value](https://github.com/AlexPikalov/cdrs/blob/master/examples/all.rs#L211) | 30 | --------------------------------------------------------------------------------