├── .gitignore ├── src ├── expressions │ └── regex_flag.rs ├── msgpack │ └── mod.rs ├── user.rs ├── batch │ ├── mod.rs │ └── batch_read.rs ├── policy │ ├── admin_policy.rs │ ├── priority.rs │ ├── commit_level.rs │ ├── consistency_level.rs │ ├── generation_policy.rs │ ├── read_policy.rs │ ├── expiration.rs │ ├── record_exists_action.rs │ ├── concurrency.rs │ ├── query_policy.rs │ ├── batch_policy.rs │ ├── scan_policy.rs │ ├── write_policy.rs │ └── client_policy.rs ├── net │ ├── mod.rs │ ├── host.rs │ └── connection.rs ├── task │ ├── mod.rs │ ├── task.rs │ ├── register_task.rs │ └── index_task.rs ├── query │ ├── mod.rs │ ├── udf.rs │ ├── index_types.rs │ ├── recordset.rs │ └── statement.rs ├── commands │ ├── field_type.rs │ ├── operate_command.rs │ ├── query_command.rs │ ├── mod.rs │ ├── touch_command.rs │ ├── particle_type.rs │ ├── execute_udf_command.rs │ ├── scan_command.rs │ ├── exists_command.rs │ ├── delete_command.rs │ ├── write_command.rs │ ├── info_command.rs │ └── single_command.rs ├── operations │ ├── cdt.rs │ ├── scalar.rs │ ├── cdt_context.rs │ └── exp.rs ├── cluster │ ├── partition.rs │ ├── partition_tokenizer.rs │ └── node_validator.rs ├── bin.rs ├── record.rs └── errors.rs ├── tools └── benchmark │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── percent.rs │ ├── main.rs │ ├── generator.rs │ └── cli.rs ├── LICENSE.md ├── tests ├── lib.rs ├── src │ ├── mod.rs │ ├── truncate.rs │ ├── exp_op.rs │ ├── index.rs │ ├── serialization.rs │ ├── batch.rs │ ├── udf.rs │ ├── task.rs │ ├── kv.rs │ ├── scan.rs │ └── hll.rs ├── client.rs └── common │ └── mod.rs ├── Cargo.toml ├── .github └── workflows │ └── build.yml ├── benches └── client_server.rs ├── .appveyor.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.todo 4 | aerospike-rust-client.sublime-project 5 | aerospike-rust-client.sublime-workspace 6 | -------------------------------------------------------------------------------- /src/expressions/regex_flag.rs: -------------------------------------------------------------------------------- 1 | //! Regex Bit Flags 2 | /// Used to change the Regex Mode in Filters 3 | pub enum RegexFlag { 4 | /// Use regex defaults. 5 | NONE = 0, 6 | /// Use POSIX Extended Regular Expression syntax when interpreting regex. 7 | EXTENDED = 1, 8 | /// Do not differentiate case. 9 | ICASE = 2, 10 | /// Do not report position of matches. 11 | NOSUB = 3, 12 | /// Match-any-character operators don't match a newline. 13 | NEWLINE = 8, 14 | } 15 | -------------------------------------------------------------------------------- /tools/benchmark/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aerospike-client-rust-benchmark" 3 | version = "0.1.0" 4 | description = "Benchmark suite for the Aerospike Rust client" 5 | authors = ["Jan Hecking "] 6 | homepage = "https://www.aerospike.com/" 7 | repository = "https://github.com/aerospike/aerospike-client-rust/" 8 | license = "Apache-2.0" 9 | 10 | [dependencies] 11 | clap = "2.33" 12 | log = "0.4" 13 | env_logger = "0.7" 14 | lazy_static = "1.4" 15 | num_cpus = "1.11" 16 | rand = "0.7" 17 | aerospike = { path = "../.." } 18 | 19 | [[bin]] 20 | path = "src/main.rs" 21 | name = "benchmark" 22 | -------------------------------------------------------------------------------- /src/msgpack/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | pub mod decoder; 17 | pub mod encoder; 18 | -------------------------------------------------------------------------------- /src/user.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// User and assigned roles. 16 | pub struct User { 17 | /// User name. 18 | pub user: String, 19 | 20 | /// List of assigned roles. 21 | pub roles: Vec, 22 | } 23 | -------------------------------------------------------------------------------- /tools/benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark Tool 2 | 3 | The benchmark tool is intended to generate artificial yet customizable load on your 4 | database cluster to help you tweak your connection properties. 5 | 6 | ## Usage 7 | 8 | To see available switches: 9 | 10 | cargo run -- --help 11 | 12 | The benchmark should be run in release mode with optimizations enabled: 13 | 14 | cargo run --release -- 15 | 16 | ## How it works 17 | 18 | By default, load is generated on keys with values in key range (`-k` switch). 19 | Bin data consists of random 64 bit integer values. 20 | 21 | ## Examples 22 | 23 | To write 10,000,000 keys to the database: 24 | 25 | $ cargo run --release -- -k 10000000 -w I 26 | 27 | To generate a load consisting 50% reads and 50% updates: 28 | 29 | $ cargo run --release -- -k 10000000 -w RU,50 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Aerospike Rust Client License 2 | ============================= 3 | 4 | Copyright 2015-2017 Aerospike, Inc. 5 | 6 | Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. 8 | 9 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | use this file except in compliance with the License. You may obtain a copy of 11 | the License at http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | License for the specific language governing permissions and limitations under 17 | the License. 18 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | extern crate env_logger; 16 | #[macro_use] 17 | extern crate lazy_static; 18 | extern crate rand; 19 | 20 | mod common; 21 | #[macro_use] 22 | mod src; 23 | -------------------------------------------------------------------------------- /src/batch/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | pub mod batch_executor; 17 | pub mod batch_read; 18 | 19 | pub use self::batch_executor::BatchExecutor; 20 | pub use self::batch_read::BatchRead; 21 | -------------------------------------------------------------------------------- /src/policy/admin_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::time::Duration; 16 | 17 | /// Policy attributes used for user administration commands. 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct AdminPolicy { 20 | /// Total transaction timeout for both client and server. 21 | pub timeout: Duration, 22 | } 23 | -------------------------------------------------------------------------------- /src/net/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | pub use self::connection::Connection; 17 | pub use self::connection_pool::ConnectionPool; 18 | pub use self::connection_pool::PooledConnection; 19 | pub use self::host::Host; 20 | pub use self::host::ToHosts; 21 | 22 | mod connection; 23 | mod connection_pool; 24 | pub mod host; 25 | mod parser; 26 | -------------------------------------------------------------------------------- /tests/src/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | mod batch; 17 | mod cdt_bitwise; 18 | mod cdt_list; 19 | mod cdt_map; 20 | mod exp; 21 | mod exp_bitwise; 22 | mod exp_hll; 23 | mod exp_list; 24 | mod exp_map; 25 | mod exp_op; 26 | mod hll; 27 | mod index; 28 | mod kv; 29 | mod query; 30 | mod scan; 31 | #[cfg(feature = "serialization")] 32 | mod serialization; 33 | mod task; 34 | mod truncate; 35 | mod udf; 36 | -------------------------------------------------------------------------------- /src/task/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | //! Types and methods used for long running status queries. 17 | #![allow(clippy::missing_errors_doc)] 18 | 19 | pub use self::index_task::IndexTask; 20 | pub use self::register_task::RegisterTask; 21 | pub use self::task::Status; 22 | pub use self::task::Task; 23 | 24 | mod index_task; 25 | mod register_task; 26 | #[allow(clippy::module_inception)] 27 | mod task; 28 | -------------------------------------------------------------------------------- /src/query/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | //! Types and methods used for database queries and scans. 17 | #![allow(clippy::missing_errors_doc)] 18 | 19 | pub use self::filter::Filter; 20 | pub use self::index_types::{CollectionIndexType, IndexType}; 21 | pub use self::recordset::Recordset; 22 | pub use self::statement::Statement; 23 | pub use self::udf::UDFLang; 24 | 25 | mod filter; 26 | mod index_types; 27 | mod recordset; 28 | mod statement; 29 | mod udf; 30 | -------------------------------------------------------------------------------- /tests/src/truncate.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use aerospike::WritePolicy; 17 | 18 | use crate::common; 19 | use env_logger; 20 | 21 | #[test] 22 | fn truncate() { 23 | let _ = env_logger::try_init(); 24 | 25 | let client = common::client(); 26 | let namespace: &str = common::namespace(); 27 | let set_name = &common::rand_str(10); 28 | let wpolicy = WritePolicy::default(); 29 | 30 | let result = client.truncate(&wpolicy, namespace, set_name, 0); 31 | assert!(result.is_ok()); 32 | } 33 | -------------------------------------------------------------------------------- /src/policy/priority.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Priority of operations on database server. 16 | #[derive(Debug, Clone)] 17 | pub enum Priority { 18 | /// Default determines that the server defines the priority. 19 | Default = 0, 20 | 21 | /// Low determines that the server should run the operation in a background thread. 22 | Low = 1, 23 | 24 | /// Medium determines that the server should run the operation at medium priority. 25 | Medium = 2, 26 | 27 | /// High determines that the server should run the operation at the highest priority. 28 | High = 3, 29 | } 30 | 31 | impl Default for Priority { 32 | fn default() -> Priority { 33 | Priority::Default 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/policy/commit_level.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | // 16 | 17 | /// `CommitLevel` determines how to handle record writes based on record generation. 18 | #[derive(Debug, PartialEq, Clone)] 19 | pub enum CommitLevel { 20 | /// CommitAll indicates the server should wait until successfully committing master and all 21 | /// replicas. 22 | CommitAll = 0, 23 | 24 | /// CommitMaster indicates the server should wait until successfully committing master only. 25 | CommitMaster, 26 | } 27 | 28 | impl Default for CommitLevel { 29 | fn default() -> CommitLevel { 30 | CommitLevel::CommitAll 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/query/udf.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::fmt; 17 | 18 | /// User-defined function (UDF) language 19 | #[derive(Debug)] 20 | pub enum UDFLang { 21 | /// Lua embedded programming language. 22 | Lua, 23 | } 24 | 25 | impl fmt::Display for UDFLang { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 27 | let s = match *self { 28 | UDFLang::Lua => "LUA", 29 | }; 30 | 31 | write!(f, "{}", s) 32 | } 33 | } 34 | 35 | impl<'a> From for &'a str { 36 | fn from(val: UDFLang) -> &'a str { 37 | match val { 38 | UDFLang::Lua => "LUA", 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aerospike" 3 | version = "1.3.0" 4 | edition = "2018" 5 | authors = ["Khosrow Afroozeh ", "Jan Hecking "] 6 | description = "Aerospike Client for Rust" 7 | keywords = ["aerospike", "nosql", "distributed", "database"] 8 | categories = ["database"] 9 | homepage = "https://www.aerospike.com/" 10 | repository = "https://github.com/aerospike/aerospike-client-rust/" 11 | documentation = "https://docs.rs/aerospike/" 12 | license = "Apache-2.0" 13 | readme = "README.md" 14 | 15 | exclude = [ 16 | ".travis.yml", 17 | ".travis/*", 18 | ".appveyor.yml", 19 | ] 20 | 21 | [badges] 22 | travis-ci = { repository = "aerospike/aerospike-client-rust" } 23 | appveyor = { repository = "aerospike/aerospike-client-rust" } 24 | 25 | [dependencies] 26 | log = "0.4" 27 | byteorder = "1.3" 28 | ripemd160 = "0.8" 29 | base64 = "0.11" 30 | crossbeam-queue = "0.2" 31 | rand = "0.7" 32 | scoped-pool = "1.0" 33 | lazy_static = "1.4" 34 | error-chain = "0.12" 35 | parking_lot = "0.9" 36 | pwhash = "0.3" 37 | serde = { version = "1.0", features = ["derive"], optional = true } 38 | 39 | [features] 40 | serialization = ["serde"] 41 | 42 | [dev-dependencies] 43 | env_logger = "0.7" 44 | hex = "0.4" 45 | bencher = "0.1" 46 | serde_json = "1.0" 47 | 48 | [[bench]] 49 | name = "client_server" 50 | harness = false 51 | 52 | [workspace] 53 | members = ["tools/benchmark"] 54 | -------------------------------------------------------------------------------- /src/policy/consistency_level.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | // 16 | 17 | /// `ConsistencyLevel` indicates how replicas should be consulted in a read 18 | /// operation to provide the desired consistency guarantee. 19 | #[derive(Debug, PartialEq, Clone)] 20 | pub enum ConsistencyLevel { 21 | /// ConsistencyOne indicates only a single replica should be consulted in 22 | /// the read operation. 23 | ConsistencyOne = 0, 24 | 25 | /// ConsistencyAll indicates that all replicas should be consulted in 26 | /// the read operation. 27 | ConsistencyAll = 1, 28 | } 29 | 30 | impl Default for ConsistencyLevel { 31 | fn default() -> ConsistencyLevel { 32 | ConsistencyLevel::ConsistencyOne 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Aerospike Rust Client Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - gh-actions 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | RUST_BACKTRACE: 1 15 | AEROSPIKE_HOSTS: 127.0.0.1:3000 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | matrix: 24 | rust: [stable] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Install Rust ${{ matrix.rust }} toolchain 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: ${{ matrix.rust }} 32 | override: true 33 | - run: rustc --version 34 | - name: Set up Aerospike Database 35 | uses: reugn/github-action-aerospike@v1 36 | - name: Clear cache 37 | run: rm -rf target/debug/deps/*aerospike* 38 | - name: Build 39 | run: cargo build --verbose 40 | - name: Run tests 41 | run: cargo test --verbose 42 | - name: Build docs 43 | run: rustdoc -L target/debug/deps/ --test README.md 44 | - name: Clear cache 45 | run: rm -rf target/debug/deps/*aerospike* 46 | - name: Build - with serialization 47 | run: cargo build --verbose --features "serialization" 48 | - name: Run tests - with serialization 49 | run: cargo test --verbose --features "serialization" 50 | - name: Build docs - with serialization 51 | run: rustdoc -L target/debug/deps/ --test README.md 52 | -------------------------------------------------------------------------------- /src/policy/generation_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | // 16 | 17 | /// `GenerationPolicy` determines how to handle record writes based on record generation. 18 | #[derive(Debug, PartialEq, Clone)] 19 | pub enum GenerationPolicy { 20 | /// None means: Do not use record generation to restrict writes. 21 | None = 0, 22 | 23 | /// ExpectGenEqual means: Update/delete record if expected generation is equal to server 24 | /// generation. Otherwise, fail. 25 | ExpectGenEqual = 1, 26 | 27 | /// ExpectGenGreater means: Update/delete record if expected generation greater than the server 28 | /// generation. Otherwise, fail. This is useful for restore after backup. 29 | ExpectGenGreater = 2, 30 | } 31 | 32 | impl Default for GenerationPolicy { 33 | fn default() -> GenerationPolicy { 34 | GenerationPolicy::None 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/field_type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | // FieldType signifies the database operation error codes. 17 | // The positive numbers align with the server side file proto.h. 18 | 19 | pub enum FieldType { 20 | Namespace = 0, 21 | Table = 1, 22 | Key = 2, 23 | // BIN = 3, 24 | DigestRipe = 4, 25 | // GUID = 5, 26 | // DigestRipeArray = 6, 27 | TranId = 7, // user supplied transaction id, which is simply passed back, 28 | // ScanOptions = 8, 29 | ScanTimeout = 9, 30 | PIDArray = 11, 31 | IndexName = 21, 32 | IndexRange = 22, 33 | // IndexFilter = 23, 34 | // IndexLimit = 24, 35 | // IndexOrderBy = 25, 36 | IndexType = 26, 37 | UdfPackageName = 30, 38 | UdfFunction = 31, 39 | UdfArgList = 32, 40 | UdfOp = 33, 41 | QueryBinList = 40, 42 | BatchIndex = 41, 43 | BatchIndexWithSet = 42, 44 | FilterExp = 43, 45 | } 46 | -------------------------------------------------------------------------------- /src/policy/read_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::expressions::FilterExpression; 17 | use crate::policy::BasePolicy; 18 | use crate::{ConsistencyLevel, Priority}; 19 | use std::time::Duration; 20 | 21 | /// `ReadPolicy` excapsulates parameters for transaction policy attributes 22 | /// used in all database operation calls. 23 | pub type ReadPolicy = BasePolicy; 24 | 25 | impl Default for ReadPolicy { 26 | fn default() -> ReadPolicy { 27 | ReadPolicy { 28 | priority: Priority::Default, 29 | timeout: Some(Duration::new(30, 0)), 30 | max_retries: Some(2), 31 | sleep_between_retries: Some(Duration::new(0, 500_000_000)), 32 | consistency_level: ConsistencyLevel::ConsistencyOne, 33 | filter_expression: None, 34 | } 35 | } 36 | } 37 | 38 | impl ReadPolicy { 39 | /// Get the Optional Filter Expression 40 | pub const fn filter_expression(&self) -> &Option { 41 | &self.filter_expression 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/src/exp_op.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use aerospike::expressions::{int_bin, int_val, num_add}; 3 | use aerospike::operations::exp::{read_exp, write_exp, ExpReadFlags, ExpWriteFlags}; 4 | use aerospike::{as_bin, as_key, as_val, Bins, ReadPolicy, WritePolicy}; 5 | 6 | #[test] 7 | fn exp_ops() { 8 | let _ = env_logger::try_init(); 9 | 10 | let client = common::client(); 11 | let namespace = common::namespace(); 12 | let set_name = &common::rand_str(10); 13 | 14 | let policy = ReadPolicy::default(); 15 | 16 | let wpolicy = WritePolicy::default(); 17 | let key = as_key!(namespace, set_name, -1); 18 | let wbin = as_bin!("bin", as_val!(25)); 19 | let bins = vec![&wbin]; 20 | 21 | client.delete(&wpolicy, &key).unwrap(); 22 | 23 | client.put(&wpolicy, &key, &bins).unwrap(); 24 | let rec = client.get(&policy, &key, Bins::All).unwrap(); 25 | assert_eq!( 26 | *rec.bins.get("bin").unwrap(), 27 | as_val!(25), 28 | "EXP OPs init failed" 29 | ); 30 | let flt = num_add(vec![int_bin("bin".to_string()), int_val(4)]); 31 | let ops = &vec![read_exp("example", &flt, ExpReadFlags::Default)]; 32 | let rec = client.operate(&wpolicy, &key, ops); 33 | let rec = rec.unwrap(); 34 | 35 | assert_eq!( 36 | *rec.bins.get("example").unwrap(), 37 | as_val!(29), 38 | "EXP OPs read failed" 39 | ); 40 | 41 | let flt2 = int_bin("bin2".to_string()); 42 | let ops = &vec![ 43 | write_exp("bin2", &flt, ExpWriteFlags::Default), 44 | read_exp("example", &flt2, ExpReadFlags::Default), 45 | ]; 46 | 47 | let rec = client.operate(&wpolicy, &key, ops); 48 | let rec = rec.unwrap(); 49 | 50 | assert_eq!( 51 | *rec.bins.get("example").unwrap(), 52 | as_val!(29), 53 | "EXP OPs write failed" 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/batch/batch_read.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::Bins; 17 | use crate::Key; 18 | use crate::Record; 19 | #[cfg(feature = "serialization")] 20 | use serde::Serialize; 21 | 22 | /// Key and bin names used in batch read commands where variable bins are needed for each key. 23 | #[cfg_attr(feature = "serialization", derive(Serialize))] 24 | pub struct BatchRead<'a> { 25 | /// Key. 26 | pub key: Key, 27 | 28 | /// Bins to retrieve for this key. 29 | pub bins: &'a Bins, 30 | 31 | /// Will contain the record after the batch read operation. 32 | pub record: Option, 33 | } 34 | 35 | impl<'a> BatchRead<'a> { 36 | /// Create a new `BatchRead` instance for the given key and bin selector. 37 | pub const fn new(key: Key, bins: &'a Bins) -> Self { 38 | BatchRead { 39 | key, 40 | bins, 41 | record: None, 42 | } 43 | } 44 | 45 | #[doc(hidden)] 46 | pub fn match_header(&self, other: &BatchRead<'a>, match_set: bool) -> bool { 47 | let key = &self.key; 48 | let other_key = &other.key; 49 | (key.namespace == other_key.namespace) 50 | && (match_set && (key.set_name == other_key.set_name)) 51 | && (self.bins == other.bins) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/policy/expiration.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::u32; 17 | 18 | const NAMESPACE_DEFAULT: u32 = 0x0000_0000; 19 | const NEVER_EXPIRE: u32 = 0xFFFF_FFFF; // -1 as i32 20 | const DONT_UPDATE: u32 = 0xFFFF_FFFE; // -2 as i32 21 | 22 | /// Record expiration, also known as time-to-live (TTL). 23 | #[derive(Debug, Clone, Copy)] 24 | pub enum Expiration { 25 | /// Set the record to expire X seconds from now 26 | Seconds(u32), 27 | 28 | /// Set the record's expiry time using the default time-to-live (TTL) value for the namespace 29 | NamespaceDefault, 30 | 31 | /// Set the record to never expire. Requires Aerospike 2 server version 2.7.2 or later or 32 | /// Aerospike 3 server version 3.1.4 or later. Do not use with older servers. 33 | Never, 34 | 35 | /// Do not change the record's expiry time when updating the record; requires Aerospike server 36 | /// version 3.10.1 or later. 37 | DontUpdate, 38 | } 39 | 40 | impl From for u32 { 41 | fn from(exp: Expiration) -> u32 { 42 | match exp { 43 | Expiration::Seconds(secs) => secs, 44 | Expiration::NamespaceDefault => NAMESPACE_DEFAULT, 45 | Expiration::Never => NEVER_EXPIRE, 46 | Expiration::DontUpdate => DONT_UPDATE, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/operations/cdt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::collections::HashMap; 17 | 18 | use crate::commands::buffer::Buffer; 19 | use crate::commands::ParticleType; 20 | use crate::errors::Result; 21 | use crate::operations::cdt_context::CdtContext; 22 | use crate::Value; 23 | 24 | #[doc(hidden)] 25 | pub enum CdtArgument<'a> { 26 | Byte(u8), 27 | Int(i64), 28 | Bool(bool), 29 | Value(&'a Value), 30 | List(&'a [Value]), 31 | Map(&'a HashMap), 32 | } 33 | 34 | pub type OperationEncoder = 35 | Box, &CdtOperation, &[CdtContext]) -> Result>; 36 | 37 | #[doc(hidden)] 38 | pub struct CdtOperation<'a> { 39 | pub op: u8, 40 | pub encoder: OperationEncoder, 41 | pub args: Vec>, 42 | } 43 | 44 | impl<'a> CdtOperation<'a> { 45 | pub const fn particle_type(&self) -> ParticleType { 46 | ParticleType::BLOB 47 | } 48 | 49 | pub fn estimate_size(&self, ctx: &[CdtContext]) -> Result { 50 | let size: usize = (self.encoder)(&mut None, self, ctx)?; 51 | Ok(size) 52 | } 53 | 54 | pub fn write_to(&self, buffer: &mut Buffer, ctx: &[CdtContext]) -> Result { 55 | let size: usize = (self.encoder)(&mut Some(buffer), self, ctx)?; 56 | Ok(size) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/policy/record_exists_action.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | // 16 | 17 | /// `RecordExistsAction` determines how to handle record writes based on record generation. 18 | #[derive(Debug, PartialEq, Clone)] 19 | pub enum RecordExistsAction { 20 | /// Update means: Create or update record. 21 | /// Merge write command bins with existing bins. 22 | Update = 0, 23 | 24 | /// UpdateOnly means: Update record only. Fail if record does not exist. 25 | /// Merge write command bins with existing bins. 26 | UpdateOnly, 27 | 28 | /// Replace means: Create or replace record. 29 | /// Delete existing bins not referenced by write command bins. 30 | /// Supported by Aerospike 2 server versions >= 2.7.5 and 31 | /// Aerospike 3 server versions >= 3.1.6. 32 | Replace, 33 | 34 | /// ReplaceOnly means: Replace record only. Fail if record does not exist. 35 | /// Delete existing bins not referenced by write command bins. 36 | /// Supported by Aerospike 2 server versions >= 2.7.5 and 37 | /// Aerospike 3 server versions >= 3.1.6. 38 | ReplaceOnly, 39 | 40 | /// CreateOnly means: Create only. Fail if record exists. 41 | CreateOnly, 42 | } 43 | 44 | impl Default for RecordExistsAction { 45 | fn default() -> RecordExistsAction { 46 | RecordExistsAction::Update 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | extern crate env_logger; 17 | #[macro_use] 18 | extern crate lazy_static; 19 | extern crate rand; 20 | 21 | use aerospike::Client; 22 | 23 | mod common; 24 | 25 | #[test] 26 | #[should_panic(expected = "Failed to connect to host(s).")] 27 | fn cluster_name() { 28 | let policy = &mut common::client_policy().clone(); 29 | policy.cluster_name = Some(String::from("notTheRealClusterName")); 30 | Client::new(policy, &common::hosts()).unwrap(); 31 | } 32 | 33 | #[test] 34 | fn node_names() { 35 | let client = common::client(); 36 | let names = client.node_names(); 37 | assert!(!names.is_empty()); 38 | } 39 | 40 | #[test] 41 | fn nodes() { 42 | let client = common::client(); 43 | let nodes = client.nodes(); 44 | assert!(!nodes.is_empty()); 45 | } 46 | 47 | #[test] 48 | fn get_node() { 49 | let client = common::client(); 50 | for name in client.node_names() { 51 | let node = client.get_node(&name); 52 | assert!(node.is_ok()); 53 | } 54 | } 55 | 56 | #[test] 57 | fn close() { 58 | let client = Client::new(common::client_policy(), &common::hosts()).unwrap(); 59 | assert_eq!(client.is_connected(), true); 60 | 61 | if let Ok(()) = client.close() { 62 | assert_eq!(client.is_connected(), false); 63 | } else { 64 | assert!(false, "Failed to close client"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/src/index.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::thread; 17 | use std::time::Duration; 18 | 19 | use crate::common; 20 | use env_logger; 21 | 22 | use aerospike::Task; 23 | use aerospike::*; 24 | 25 | const EXPECTED: usize = 100; 26 | 27 | fn create_test_set(no_records: usize) -> String { 28 | let client = common::client(); 29 | let namespace = common::namespace(); 30 | let set_name = common::rand_str(10); 31 | let wpolicy = WritePolicy::default(); 32 | 33 | for i in 0..no_records as i64 { 34 | let key = as_key!(namespace, &set_name, i); 35 | let wbin = as_bin!("bin", i); 36 | let bins = vec![&wbin]; 37 | client.delete(&wpolicy, &key).unwrap(); 38 | client.put(&wpolicy, &key, &bins).unwrap(); 39 | } 40 | 41 | set_name 42 | } 43 | 44 | #[test] 45 | fn create_index() { 46 | let _ = env_logger::try_init(); 47 | 48 | let client = common::client(); 49 | let ns = common::namespace(); 50 | let set = create_test_set(EXPECTED); 51 | let bin = "bin"; 52 | let index = format!("{}_{}_{}", ns, set, bin); 53 | let policy = WritePolicy::default(); 54 | 55 | let _ = client.drop_index(&policy, ns, &set, &index); 56 | thread::sleep(Duration::from_millis(1000)); 57 | 58 | let task = client 59 | .create_index(&policy, ns, &set, bin, &index, IndexType::Numeric) 60 | .expect("Failed to create index"); 61 | task.wait_till_complete(None).unwrap(); 62 | } 63 | -------------------------------------------------------------------------------- /tools/benchmark/src/percent.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::cmp::Ordering; 17 | use std::str::FromStr; 18 | 19 | use rand::distributions::{Distribution, Standard}; 20 | use rand::Rng; 21 | 22 | #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Debug)] 23 | pub struct Percent(u8); 24 | 25 | impl Percent { 26 | pub fn new(value: u8) -> Percent { 27 | Percent(value) 28 | } 29 | } 30 | 31 | impl FromStr for Percent { 32 | type Err = String; 33 | 34 | fn from_str(s: &str) -> Result { 35 | if let Ok(pct) = u8::from_str(s) { 36 | if pct <= 100 { 37 | return Ok(Percent(pct)); 38 | } 39 | } 40 | Err("Invalid percent value".into()) 41 | } 42 | } 43 | 44 | impl Ord for Percent { 45 | fn cmp(&self, other: &Self) -> Ordering { 46 | self.0.cmp(&other.0) 47 | } 48 | } 49 | 50 | impl Distribution for Standard { 51 | fn sample(&self, rng: &mut R) -> Percent { 52 | let r: u32 = rng.gen(); 53 | let pct = r % 101; 54 | Percent(pct as u8) 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod test { 60 | use super::*; 61 | 62 | #[test] 63 | fn test_percent_from_str() { 64 | assert_eq!(Percent::from_str("42"), Ok(Percent::new(42))); 65 | assert!(Percent::from_str("0.5").is_err()); 66 | assert!(Percent::from_str("120").is_err()); 67 | assert!(Percent::from_str("abc").is_err()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/cluster/partition.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::fmt; 16 | use std::io::Cursor; 17 | 18 | use byteorder::{LittleEndian, ReadBytesExt}; 19 | 20 | use crate::cluster::node; 21 | use crate::Key; 22 | 23 | // Validates a Database server node 24 | #[derive(Debug, Clone)] 25 | pub struct Partition<'a> { 26 | pub namespace: &'a str, 27 | pub partition_id: usize, 28 | } 29 | 30 | impl<'a> Partition<'a> { 31 | pub const fn new(namespace: &'a str, partition_id: usize) -> Self { 32 | Partition { 33 | namespace, 34 | partition_id, 35 | } 36 | } 37 | 38 | pub fn new_by_key(key: &'a Key) -> Self { 39 | let mut rdr = Cursor::new(&key.digest[0..4]); 40 | 41 | Partition { 42 | namespace: &key.namespace, 43 | 44 | // CAN'T USE MOD directly - mod will give negative numbers. 45 | // First AND makes positive and negative correctly, then mod. 46 | // For any x, y : x % 2^y = x & (2^y - 1); the second method is twice as fast 47 | partition_id: rdr.read_u32::().unwrap() as usize & (node::PARTITIONS - 1), 48 | } 49 | } 50 | } 51 | 52 | impl<'a> PartialEq for Partition<'a> { 53 | fn eq(&self, other: &Partition) -> bool { 54 | self.namespace == other.namespace && self.partition_id == other.partition_id 55 | } 56 | } 57 | 58 | impl<'a> fmt::Display for Partition<'a> { 59 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 60 | format!("Partition ({}: {})", self.namespace, self.partition_id).fmt(f) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/query/index_types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::fmt; 17 | 18 | /// Underlying data type of secondary index. 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub enum IndexType { 21 | /// Numeric index. 22 | Numeric, 23 | 24 | /// String index. 25 | String, 26 | 27 | /// 2-dimensional spherical geospatial index. 28 | Geo2DSphere, 29 | } 30 | 31 | /// Secondary index collection type. 32 | #[derive(Debug, Clone, PartialEq)] 33 | pub enum CollectionIndexType { 34 | /// Normal, scalar index. 35 | Default = 0, 36 | 37 | /// Index list elements. 38 | List, 39 | 40 | /// Index map keys. 41 | MapKeys, 42 | 43 | /// Index map values. 44 | MapValues, 45 | } 46 | 47 | impl fmt::Display for IndexType { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 49 | match *self { 50 | IndexType::Numeric => "NUMERIC".fmt(f), 51 | IndexType::String => "STRING".fmt(f), 52 | IndexType::Geo2DSphere => "GEO2DSPHERE".fmt(f), 53 | } 54 | } 55 | } 56 | 57 | impl fmt::Display for CollectionIndexType { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 59 | match *self { 60 | CollectionIndexType::Default => panic!("Unknown IndexCollectionType value `Default`"), 61 | CollectionIndexType::List => "LIST".fmt(f), 62 | CollectionIndexType::MapKeys => "MAPKEYS".fmt(f), 63 | CollectionIndexType::MapValues => "MAPVALUES".fmt(f), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/policy/concurrency.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | /// Specifies whether a command, that needs to be executed on multiple cluster nodes, should be 17 | /// executed sequentially, one node at a time, or in parallel on multiple nodes using the client's 18 | /// thread pool. 19 | #[derive(Debug, Clone, Copy)] 20 | pub enum Concurrency { 21 | /// Issue commands sequentially. This mode has a performance advantage for small to 22 | /// medium sized batch sizes because requests can be issued in the main transaction thread. 23 | /// This is the default. 24 | Sequential, 25 | 26 | /// Issue all commands in parallel threads. This mode has a performance advantage for 27 | /// extremely large batch sizes because each node can process the request immediately. The 28 | /// downside is extra threads will need to be created (or takedn from a thread pool). 29 | Parallel, 30 | 31 | /// Issue up to N commands in parallel threads. When a request completes, a new request 32 | /// will be issued until all threads are complete. This mode prevents too many parallel threads 33 | /// being created for large cluster implementations. The downside is extra threads will still 34 | /// need to be created (or taken from a thread pool). 35 | /// 36 | /// E.g. if there are 16 nodes/namespace combinations requested and concurrency is set to 37 | /// `MaxThreads(8)`, then batch requests will be made for 8 node/namespace combinations in 38 | /// parallel threads. When a request completes, a new request will be issued until all 16 39 | /// requests are complete. 40 | MaxThreads(usize), 41 | } 42 | -------------------------------------------------------------------------------- /tools/benchmark/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | #[macro_use] 17 | extern crate aerospike; 18 | #[macro_use] 19 | extern crate clap; 20 | extern crate env_logger; 21 | #[macro_use] 22 | extern crate lazy_static; 23 | #[macro_use] 24 | extern crate log; 25 | extern crate num_cpus; 26 | extern crate rand; 27 | 28 | mod cli; 29 | mod generator; 30 | mod percent; 31 | mod stats; 32 | mod workers; 33 | 34 | use std::sync::mpsc; 35 | use std::sync::Arc; 36 | use std::thread; 37 | 38 | use aerospike::{Client, ClientPolicy}; 39 | 40 | use cli::Options; 41 | use generator::KeyPartitions; 42 | use stats::Collector; 43 | use workers::Worker; 44 | 45 | fn main() { 46 | let _ = env_logger::try_init(); 47 | let options = cli::parse_options(); 48 | info!("{:?}", options); 49 | let client = connect(&options); 50 | run_workload(client, options); 51 | } 52 | 53 | fn connect(options: &Options) -> Client { 54 | let mut policy = ClientPolicy::default(); 55 | policy.conn_pools_per_node = options.conn_pools_per_node; 56 | Client::new(&policy, &options.hosts).unwrap() 57 | } 58 | 59 | fn run_workload(client: Client, opts: Options) { 60 | let client = Arc::new(client); 61 | let (send, recv) = mpsc::channel(); 62 | let collector = Collector::new(recv); 63 | for keys in KeyPartitions::new( 64 | opts.namespace, 65 | opts.set, 66 | opts.start_key, 67 | opts.keys, 68 | opts.concurrency, 69 | ) { 70 | let mut worker = Worker::for_workload(&opts.workload, client.clone(), send.clone()); 71 | thread::spawn(move || worker.run(keys)); 72 | } 73 | drop(send); 74 | collector.collect(); 75 | } 76 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | #![allow(dead_code)] 17 | 18 | use std::env; 19 | use std::sync::Arc; 20 | 21 | use rand; 22 | use rand::distributions::Alphanumeric; 23 | use rand::Rng; 24 | 25 | use aerospike::{Client, ClientPolicy}; 26 | 27 | lazy_static! { 28 | static ref AEROSPIKE_HOSTS: String = 29 | env::var("AEROSPIKE_HOSTS").unwrap_or_else(|_| String::from("127.0.0.1")); 30 | static ref AEROSPIKE_NAMESPACE: String = 31 | env::var("AEROSPIKE_NAMESPACE").unwrap_or_else(|_| String::from("test")); 32 | static ref AEROSPIKE_CLUSTER: Option = env::var("AEROSPIKE_CLUSTER").ok(); 33 | static ref GLOBAL_CLIENT_POLICY: ClientPolicy = { 34 | let mut policy = ClientPolicy::default(); 35 | if let Ok(user) = env::var("AEROSPIKE_USER") { 36 | let password = env::var("AEROSPIKE_PASSWORD").unwrap_or_default(); 37 | policy.set_user_password(user, password).unwrap(); 38 | } 39 | policy.cluster_name = AEROSPIKE_CLUSTER.clone(); 40 | policy 41 | }; 42 | static ref GLOBAL_CLIENT: Arc = 43 | Arc::new(Client::new(&GLOBAL_CLIENT_POLICY, &*AEROSPIKE_HOSTS).unwrap()); 44 | } 45 | 46 | pub fn hosts() -> &'static str { 47 | &*AEROSPIKE_HOSTS 48 | } 49 | 50 | pub fn namespace() -> &'static str { 51 | &*AEROSPIKE_NAMESPACE 52 | } 53 | 54 | pub fn client_policy() -> &'static ClientPolicy { 55 | &*GLOBAL_CLIENT_POLICY 56 | } 57 | 58 | pub fn client() -> Arc { 59 | GLOBAL_CLIENT.clone() 60 | } 61 | 62 | pub fn rand_str(sz: usize) -> String { 63 | let rng = rand::thread_rng(); 64 | rng.sample_iter(&Alphanumeric).take(sz).collect() 65 | } 66 | -------------------------------------------------------------------------------- /src/task/task.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::errors::{ErrorKind, Result}; 17 | use std::thread; 18 | use std::time::{Duration, Instant}; 19 | 20 | /// Status of task 21 | #[derive(Debug, Clone, Copy)] 22 | pub enum Status { 23 | /// long running task not found 24 | NotFound, 25 | /// long running task in progress 26 | InProgress, 27 | /// long running task completed 28 | Complete, 29 | } 30 | 31 | static POLL_INTERVAL: Duration = Duration::from_secs(1); 32 | 33 | /// Base task interface 34 | pub trait Task { 35 | /// interface for query specific task status 36 | fn query_status(&self) -> Result; 37 | 38 | /// Wait until query status is complete, an error occurs, or the timeout has elapsed. 39 | fn wait_till_complete(&self, timeout: Option) -> Result { 40 | let now = Instant::now(); 41 | let timeout_elapsed = |deadline| now.elapsed() + POLL_INTERVAL > deadline; 42 | 43 | loop { 44 | // Sleep first to give task a chance to complete and help avoid case where task hasn't 45 | // started yet. 46 | thread::sleep(POLL_INTERVAL); 47 | 48 | match self.query_status() { 49 | Ok(Status::NotFound) => { 50 | bail!(ErrorKind::BadResponse("task status not found".to_string())) 51 | } 52 | Ok(Status::InProgress) => {} // do nothing and wait 53 | error_or_complete => return error_or_complete, 54 | } 55 | 56 | if timeout.map_or(false, timeout_elapsed) { 57 | bail!(ErrorKind::Timeout("Task timeout reached".to_string())) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/task/register_task.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::cluster::Cluster; 17 | use crate::errors::{ErrorKind, Result}; 18 | use crate::task::{Status, Task}; 19 | use std::sync::Arc; 20 | 21 | /// Struct for querying udf register status 22 | #[derive(Debug, Clone)] 23 | pub struct RegisterTask { 24 | cluster: Arc, 25 | package_name: String, 26 | } 27 | 28 | static COMMAND: &str = "udf-list"; 29 | static RESPONSE_PATTERN: &str = "filename="; 30 | 31 | impl RegisterTask { 32 | /// Initializes `RegisterTask` from client, creation should only be expose to Client 33 | pub fn new(cluster: Arc, package_name: String) -> Self { 34 | RegisterTask { 35 | cluster, 36 | package_name, 37 | } 38 | } 39 | } 40 | 41 | impl Task for RegisterTask { 42 | /// Query the status of index creation across all nodes 43 | fn query_status(&self) -> Result { 44 | let nodes = self.cluster.nodes(); 45 | 46 | if nodes.is_empty() { 47 | bail!(ErrorKind::Connection("No connected node".to_string())) 48 | } 49 | 50 | for node in &nodes { 51 | let response = node.info( 52 | Some(self.cluster.client_policy().timeout.unwrap()), 53 | &[&COMMAND[..]], 54 | )?; 55 | 56 | if !response.contains_key(COMMAND) { 57 | return Ok(Status::NotFound); 58 | } 59 | 60 | let response_find = format!("{}{}", RESPONSE_PATTERN, self.package_name); 61 | if response[COMMAND].find(&response_find).is_none() { 62 | return Ok(Status::InProgress); 63 | } 64 | } 65 | Ok(Status::Complete) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/commands/operate_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::{Cluster, Node}; 19 | use crate::commands::{Command, ReadCommand, SingleCommand}; 20 | use crate::errors::Result; 21 | use crate::net::Connection; 22 | use crate::operations::Operation; 23 | use crate::policy::WritePolicy; 24 | use crate::{Bins, Key}; 25 | 26 | pub struct OperateCommand<'a> { 27 | pub read_command: ReadCommand<'a>, 28 | policy: &'a WritePolicy, 29 | operations: &'a [Operation<'a>], 30 | } 31 | 32 | impl<'a> OperateCommand<'a> { 33 | pub fn new( 34 | policy: &'a WritePolicy, 35 | cluster: Arc, 36 | key: &'a Key, 37 | operations: &'a [Operation<'a>], 38 | ) -> Self { 39 | OperateCommand { 40 | read_command: ReadCommand::new(&policy.base_policy, cluster, key, Bins::All), 41 | policy, 42 | operations, 43 | } 44 | } 45 | 46 | pub fn execute(&mut self) -> Result<()> { 47 | SingleCommand::execute(self.policy, self) 48 | } 49 | } 50 | 51 | impl<'a> Command for OperateCommand<'a> { 52 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 53 | conn.buffer.write_timeout(timeout); 54 | Ok(()) 55 | } 56 | 57 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 58 | conn.flush() 59 | } 60 | 61 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 62 | conn.buffer.set_operate( 63 | self.policy, 64 | self.read_command.single_command.key, 65 | self.operations, 66 | ) 67 | } 68 | 69 | fn get_node(&self) -> Result> { 70 | self.read_command.get_node() 71 | } 72 | 73 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 74 | self.read_command.parse_result(conn) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/src/serialization.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | use aerospike::{ 16 | as_bin, as_blob, as_geo, as_key, as_list, as_map, as_val, Bins, ReadPolicy, WritePolicy, 17 | }; 18 | use env_logger; 19 | 20 | use crate::common; 21 | 22 | #[test] 23 | fn serialize() { 24 | let _ = env_logger::try_init(); 25 | 26 | let client = common::client(); 27 | let namespace: &str = common::namespace(); 28 | let set_name = &common::rand_str(10); 29 | let policy = ReadPolicy::default(); 30 | let wpolicy = WritePolicy::default(); 31 | let key = as_key!(namespace, set_name, -1); 32 | 33 | client.delete(&wpolicy, &key).unwrap(); 34 | 35 | let bins = [ 36 | as_bin!("bin999", "test string"), 37 | as_bin!("bin vec![int]", as_list![1u32, 2u32, 3u32]), 38 | as_bin!("bin vec![u8]", as_blob!(vec![1u8, 2u8, 3u8])), 39 | as_bin!("bin map", as_map!(1 => 1, 2 => 2, 3 => "hi!")), 40 | as_bin!("bin f64", 1.64f64), 41 | as_bin!("bin Nil", None), // Writing None erases the bin! 42 | as_bin!( 43 | "bin Geo", 44 | as_geo!(format!( 45 | r#"{{ "type": "Point", "coordinates": [{}, {}] }}"#, 46 | 17.119_381, 19.45612 47 | )) 48 | ), 49 | as_bin!("bin-name-len-15", "max. bin name length is 15 chars"), 50 | ]; 51 | client.put(&wpolicy, &key, &bins).unwrap(); 52 | 53 | let record = client.get(&policy, &key, Bins::All).unwrap(); 54 | 55 | let json = serde_json::to_string(&record); 56 | if json.is_err() { 57 | assert!(false, "JSON Parsing of the Record was not successful") 58 | } 59 | let json = serde_json::to_string(&record.bins.get("bin999")); 60 | assert_eq!( 61 | json.unwrap(), 62 | "\"test string\"", 63 | "The Parsed JSON value for bin999 did not match" 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/commands/query_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::Node; 19 | use crate::commands::{Command, SingleCommand, StreamCommand}; 20 | use crate::errors::Result; 21 | use crate::net::Connection; 22 | use crate::policy::QueryPolicy; 23 | use crate::{Recordset, Statement}; 24 | 25 | pub struct QueryCommand<'a> { 26 | stream_command: StreamCommand, 27 | policy: &'a QueryPolicy, 28 | statement: Arc, 29 | partitions: Vec, 30 | } 31 | 32 | impl<'a> QueryCommand<'a> { 33 | pub fn new( 34 | policy: &'a QueryPolicy, 35 | node: Arc, 36 | statement: Arc, 37 | recordset: Arc, 38 | partitions: Vec, 39 | ) -> Self { 40 | QueryCommand { 41 | stream_command: StreamCommand::new(node, recordset), 42 | policy, 43 | statement, 44 | partitions, 45 | } 46 | } 47 | 48 | pub fn execute(&mut self) -> Result<()> { 49 | SingleCommand::execute(self.policy, self) 50 | } 51 | } 52 | 53 | impl<'a> Command for QueryCommand<'a> { 54 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 55 | conn.buffer.write_timeout(timeout); 56 | Ok(()) 57 | } 58 | 59 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 60 | conn.flush() 61 | } 62 | 63 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 64 | conn.buffer.set_query( 65 | self.policy, 66 | &self.statement, 67 | false, 68 | self.stream_command.recordset.task_id(), 69 | &self.partitions, 70 | ) 71 | } 72 | 73 | fn get_node(&self) -> Result> { 74 | self.stream_command.get_node() 75 | } 76 | 77 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 78 | StreamCommand::parse_result(&mut self.stream_command, conn) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | pub mod admin_command; 16 | pub mod batch_read_command; 17 | pub mod buffer; 18 | pub mod delete_command; 19 | pub mod execute_udf_command; 20 | pub mod exists_command; 21 | pub mod info_command; 22 | pub mod operate_command; 23 | pub mod particle_type; 24 | pub mod query_command; 25 | pub mod read_command; 26 | pub mod scan_command; 27 | pub mod single_command; 28 | pub mod stream_command; 29 | pub mod touch_command; 30 | pub mod write_command; 31 | 32 | mod field_type; 33 | 34 | use std::sync::Arc; 35 | use std::time::Duration; 36 | 37 | pub use self::batch_read_command::BatchReadCommand; 38 | pub use self::delete_command::DeleteCommand; 39 | pub use self::execute_udf_command::ExecuteUDFCommand; 40 | pub use self::exists_command::ExistsCommand; 41 | pub use self::info_command::Message; 42 | pub use self::operate_command::OperateCommand; 43 | pub use self::particle_type::ParticleType; 44 | pub use self::query_command::QueryCommand; 45 | pub use self::read_command::ReadCommand; 46 | pub use self::scan_command::ScanCommand; 47 | pub use self::single_command::SingleCommand; 48 | pub use self::stream_command::StreamCommand; 49 | pub use self::touch_command::TouchCommand; 50 | pub use self::write_command::WriteCommand; 51 | 52 | use crate::cluster::Node; 53 | use crate::errors::{Error, ErrorKind, Result}; 54 | use crate::net::Connection; 55 | use crate::ResultCode; 56 | 57 | // Command interface describes all commands available 58 | pub trait Command { 59 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()>; 60 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()>; 61 | fn get_node(&self) -> Result>; 62 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()>; 63 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()>; 64 | } 65 | 66 | pub const fn keep_connection(err: &Error) -> bool { 67 | match *err { 68 | Error(ErrorKind::ServerError(result_code), _) => { 69 | matches!(result_code, ResultCode::KeyNotFoundError) 70 | } 71 | _ => false, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/commands/touch_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::{Cluster, Node}; 19 | use crate::commands::buffer; 20 | use crate::commands::{Command, SingleCommand}; 21 | use crate::errors::{ErrorKind, Result}; 22 | use crate::net::Connection; 23 | use crate::policy::WritePolicy; 24 | use crate::{Key, ResultCode}; 25 | 26 | pub struct TouchCommand<'a> { 27 | single_command: SingleCommand<'a>, 28 | policy: &'a WritePolicy, 29 | } 30 | 31 | impl<'a> TouchCommand<'a> { 32 | pub fn new(policy: &'a WritePolicy, cluster: Arc, key: &'a Key) -> Self { 33 | TouchCommand { 34 | single_command: SingleCommand::new(cluster, key), 35 | policy, 36 | } 37 | } 38 | 39 | pub fn execute(&mut self) -> Result<()> { 40 | SingleCommand::execute(self.policy, self) 41 | } 42 | } 43 | 44 | impl<'a> Command for TouchCommand<'a> { 45 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 46 | conn.buffer.write_timeout(timeout); 47 | Ok(()) 48 | } 49 | 50 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 51 | conn.flush() 52 | } 53 | 54 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 55 | conn.buffer.set_touch(self.policy, self.single_command.key) 56 | } 57 | 58 | fn get_node(&self) -> Result> { 59 | self.single_command.get_node() 60 | } 61 | 62 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 63 | // Read header. 64 | if let Err(err) = conn.read_buffer(buffer::MSG_TOTAL_HEADER_SIZE as usize) { 65 | warn!("Parse result error: {}", err); 66 | return Err(err); 67 | } 68 | 69 | conn.buffer.reset_offset()?; 70 | 71 | let result_code = ResultCode::from(conn.buffer.read_u8(Some(13))?); 72 | if result_code != ResultCode::Ok { 73 | bail!(ErrorKind::ServerError(result_code)); 74 | } 75 | 76 | SingleCommand::empty_socket(conn) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/commands/particle_type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::fmt; 17 | use std::result::Result as StdResult; 18 | 19 | #[derive(Debug, Clone)] 20 | #[doc(hidden)] 21 | pub enum ParticleType { 22 | // Server particle types. Unsupported types are commented out. 23 | NULL = 0, 24 | INTEGER = 1, 25 | FLOAT = 2, 26 | STRING = 3, 27 | BLOB = 4, 28 | DIGEST = 6, 29 | BOOL = 17, 30 | HLL = 18, 31 | MAP = 19, 32 | LIST = 20, 33 | LDT = 21, 34 | GEOJSON = 23, 35 | } 36 | 37 | impl From for ParticleType { 38 | fn from(val: u8) -> ParticleType { 39 | match val { 40 | 0 => ParticleType::NULL, 41 | 1 => ParticleType::INTEGER, 42 | 2 => ParticleType::FLOAT, 43 | 3 => ParticleType::STRING, 44 | 4 => ParticleType::BLOB, 45 | 6 => ParticleType::DIGEST, 46 | 17 => ParticleType::BOOL, 47 | 18 => ParticleType::HLL, 48 | 19 => ParticleType::MAP, 49 | 20 => ParticleType::LIST, 50 | 21 => ParticleType::LDT, 51 | 23 => ParticleType::GEOJSON, 52 | _ => unreachable!(), 53 | } 54 | } 55 | } 56 | 57 | impl fmt::Display for ParticleType { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> { 59 | match self { 60 | ParticleType::NULL => write!(f, "NULL"), 61 | ParticleType::INTEGER => write!(f, "INTEGER"), 62 | ParticleType::FLOAT => write!(f, "FLOAT"), 63 | ParticleType::STRING => write!(f, "STRING"), 64 | ParticleType::BLOB => write!(f, "BLOB"), 65 | ParticleType::DIGEST => write!(f, "DIGEST"), 66 | ParticleType::BOOL => write!(f, "BOOL"), 67 | ParticleType::HLL => write!(f, "HLL"), 68 | ParticleType::MAP => write!(f, "MAP"), 69 | ParticleType::LIST => write!(f, "LIST"), 70 | ParticleType::LDT => write!(f, "LDT"), 71 | ParticleType::GEOJSON => write!(f, "GEOJSON"), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/commands/execute_udf_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::str; 16 | use std::sync::Arc; 17 | use std::time::Duration; 18 | 19 | use crate::cluster::{Cluster, Node}; 20 | use crate::commands::{Command, ReadCommand, SingleCommand}; 21 | use crate::errors::Result; 22 | use crate::net::Connection; 23 | use crate::policy::WritePolicy; 24 | use crate::{Bins, Key, Value}; 25 | 26 | pub struct ExecuteUDFCommand<'a> { 27 | pub read_command: ReadCommand<'a>, 28 | policy: &'a WritePolicy, 29 | package_name: &'a str, 30 | function_name: &'a str, 31 | args: Option<&'a [Value]>, 32 | } 33 | 34 | impl<'a> ExecuteUDFCommand<'a> { 35 | pub fn new( 36 | policy: &'a WritePolicy, 37 | cluster: Arc, 38 | key: &'a Key, 39 | package_name: &'a str, 40 | function_name: &'a str, 41 | args: Option<&'a [Value]>, 42 | ) -> Self { 43 | ExecuteUDFCommand { 44 | read_command: ReadCommand::new(&policy.base_policy, cluster, key, Bins::All), 45 | policy, 46 | package_name, 47 | function_name, 48 | args, 49 | } 50 | } 51 | 52 | pub fn execute(&mut self) -> Result<()> { 53 | SingleCommand::execute(self.policy, self) 54 | } 55 | } 56 | 57 | impl<'a> Command for ExecuteUDFCommand<'a> { 58 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 59 | conn.buffer.write_timeout(timeout); 60 | Ok(()) 61 | } 62 | 63 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 64 | conn.flush() 65 | } 66 | 67 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 68 | conn.buffer.set_udf( 69 | self.policy, 70 | self.read_command.single_command.key, 71 | self.package_name, 72 | self.function_name, 73 | self.args, 74 | ) 75 | } 76 | 77 | fn get_node(&self) -> Result> { 78 | self.read_command.get_node() 79 | } 80 | 81 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 82 | self.read_command.parse_result(conn) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/commands/scan_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::str; 16 | use std::sync::Arc; 17 | use std::time::Duration; 18 | 19 | use crate::cluster::Node; 20 | use crate::commands::{Command, SingleCommand, StreamCommand}; 21 | use crate::errors::Result; 22 | use crate::net::Connection; 23 | use crate::policy::ScanPolicy; 24 | use crate::{Bins, Recordset}; 25 | 26 | pub struct ScanCommand<'a> { 27 | stream_command: StreamCommand, 28 | policy: &'a ScanPolicy, 29 | namespace: &'a str, 30 | set_name: &'a str, 31 | bins: Bins, 32 | partitions: Vec, 33 | } 34 | 35 | impl<'a> ScanCommand<'a> { 36 | pub fn new( 37 | policy: &'a ScanPolicy, 38 | node: Arc, 39 | namespace: &'a str, 40 | set_name: &'a str, 41 | bins: Bins, 42 | recordset: Arc, 43 | partitions: Vec, 44 | ) -> Self { 45 | ScanCommand { 46 | stream_command: StreamCommand::new(node, recordset), 47 | policy, 48 | namespace, 49 | set_name, 50 | bins, 51 | partitions, 52 | } 53 | } 54 | 55 | pub fn execute(&mut self) -> Result<()> { 56 | SingleCommand::execute(self.policy, self) 57 | } 58 | } 59 | 60 | impl<'a> Command for ScanCommand<'a> { 61 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 62 | conn.buffer.write_timeout(timeout); 63 | Ok(()) 64 | } 65 | 66 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 67 | conn.flush() 68 | } 69 | 70 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 71 | conn.buffer.set_scan( 72 | self.policy, 73 | self.namespace, 74 | self.set_name, 75 | &self.bins, 76 | self.stream_command.recordset.task_id(), 77 | &self.partitions, 78 | ) 79 | } 80 | 81 | fn get_node(&self) -> Result> { 82 | self.stream_command.get_node() 83 | } 84 | 85 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 86 | StreamCommand::parse_result(&mut self.stream_command, conn) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/policy/query_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::expressions::FilterExpression; 17 | use crate::policy::{BasePolicy, PolicyLike}; 18 | 19 | /// `QueryPolicy` encapsulates parameters for query operations. 20 | #[derive(Debug, Clone)] 21 | pub struct QueryPolicy { 22 | /// Base policy instance 23 | pub base_policy: BasePolicy, 24 | 25 | /// Maximum number of concurrent requests to server nodes at any point in time. If there are 16 26 | /// nodes in the cluster and `max_concurrent_nodes` is 8, then queries will be made to 8 nodes 27 | /// in parallel. When a query completes, a new query will be issued until all 16 nodes have 28 | /// been queried. Default (0) is to issue requests to all server nodes in parallel. 29 | pub max_concurrent_nodes: usize, 30 | 31 | /// Number of records to place in queue before blocking. Records received from multiple server 32 | /// nodes will be placed in a queue. A separate thread consumes these records in parallel. If 33 | /// the queue is full, the producer threads will block until records are consumed. 34 | pub record_queue_size: usize, 35 | 36 | /// Terminate query if cluster is in fluctuating state. 37 | pub fail_on_cluster_change: bool, 38 | 39 | /// Optional Filter Expression 40 | pub filter_expression: Option, 41 | } 42 | 43 | impl QueryPolicy { 44 | /// Create a new query policy instance with default parameters. 45 | pub fn new() -> Self { 46 | QueryPolicy::default() 47 | } 48 | 49 | /// Get the current Filter Expression 50 | pub const fn filter_expression(&self) -> &Option { 51 | &self.filter_expression 52 | } 53 | } 54 | 55 | impl Default for QueryPolicy { 56 | fn default() -> Self { 57 | QueryPolicy { 58 | base_policy: BasePolicy::default(), 59 | max_concurrent_nodes: 0, 60 | record_queue_size: 1024, 61 | fail_on_cluster_change: true, 62 | filter_expression: None, 63 | } 64 | } 65 | } 66 | 67 | impl PolicyLike for QueryPolicy { 68 | fn base(&self) -> &BasePolicy { 69 | &self.base_policy 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /benches/client_server.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | #[macro_use] 17 | extern crate bencher; 18 | #[macro_use] 19 | extern crate lazy_static; 20 | extern crate rand; 21 | 22 | use aerospike::{Bins, ReadPolicy, WritePolicy}; 23 | 24 | use aerospike::{as_bin, as_key}; 25 | use bencher::Bencher; 26 | 27 | #[path = "../tests/common/mod.rs"] 28 | mod common; 29 | 30 | lazy_static! { 31 | static ref TEST_SET: String = common::rand_str(10); 32 | } 33 | 34 | fn single_key_read(bench: &mut Bencher) { 35 | let client = common::client(); 36 | let namespace = common::namespace(); 37 | let key = as_key!(namespace, &TEST_SET, common::rand_str(10)); 38 | let wbin = as_bin!("i", 1); 39 | let bins = vec![&wbin]; 40 | let rpolicy = ReadPolicy::default(); 41 | let wpolicy = WritePolicy::default(); 42 | client.put(&wpolicy, &key, &bins).unwrap(); 43 | 44 | bench.iter(|| client.get(&rpolicy, &key, Bins::All).unwrap()); 45 | } 46 | 47 | fn single_key_read_header(bench: &mut Bencher) { 48 | let client = common::client(); 49 | let namespace = common::namespace(); 50 | let key = as_key!(namespace, &TEST_SET, common::rand_str(10)); 51 | let wbin = as_bin!("i", 1); 52 | let bins = vec![&wbin]; 53 | let rpolicy = ReadPolicy::default(); 54 | let wpolicy = WritePolicy::default(); 55 | client.put(&wpolicy, &key, &bins).unwrap(); 56 | 57 | bench.iter(|| client.get(&rpolicy, &key, Bins::None).unwrap()); 58 | } 59 | 60 | fn single_key_write(bench: &mut Bencher) { 61 | let client = common::client(); 62 | let namespace = common::namespace(); 63 | let key = as_key!(namespace, &TEST_SET, common::rand_str(10)); 64 | let wpolicy = WritePolicy::default(); 65 | 66 | let bin1 = as_bin!("str1", common::rand_str(256)); 67 | let bin2 = as_bin!("str1", common::rand_str(256)); 68 | let bin3 = as_bin!("str1", common::rand_str(256)); 69 | let bin4 = as_bin!("str1", common::rand_str(256)); 70 | let bins = [bin1, bin2, bin3, bin4]; 71 | 72 | bench.iter(|| { 73 | client.put(&wpolicy, &key, &bins).unwrap(); 74 | }); 75 | } 76 | 77 | benchmark_group!( 78 | benches, 79 | single_key_read, 80 | single_key_read_header, 81 | single_key_write, 82 | ); 83 | benchmark_main!(benches); 84 | -------------------------------------------------------------------------------- /tests/src/batch.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use aerospike::BatchRead; 17 | use aerospike::Bins; 18 | use aerospike::{as_bin, as_key, BatchPolicy, Concurrency, WritePolicy}; 19 | 20 | use env_logger; 21 | 22 | use crate::common; 23 | 24 | #[test] 25 | fn batch_get() { 26 | let _ = env_logger::try_init(); 27 | 28 | let client = common::client(); 29 | let namespace: &str = common::namespace(); 30 | let set_name = &common::rand_str(10); 31 | let mut bpolicy = BatchPolicy::default(); 32 | bpolicy.concurrency = Concurrency::Parallel; 33 | let wpolicy = WritePolicy::default(); 34 | 35 | let bin1 = as_bin!("a", "a value"); 36 | let bin2 = as_bin!("b", "another value"); 37 | let bin3 = as_bin!("c", 42); 38 | 39 | let key1 = as_key!(namespace, set_name, 1); 40 | client.put(&wpolicy, &key1, &[&bin1, &bin2, &bin3]).unwrap(); 41 | 42 | let key2 = as_key!(namespace, set_name, 2); 43 | client.put(&wpolicy, &key2, &[&bin1, &bin2, &bin3]).unwrap(); 44 | 45 | let key3 = as_key!(namespace, set_name, 3); 46 | client.put(&wpolicy, &key3, &[&bin1, &bin2, &bin3]).unwrap(); 47 | 48 | let key4 = as_key!(namespace, set_name, -1); 49 | // key does not exist 50 | 51 | let selected = Bins::from(["a"]); 52 | let all = Bins::All; 53 | let none = Bins::None; 54 | 55 | let batch = vec![ 56 | BatchRead::new(key1.clone(), &selected), 57 | BatchRead::new(key2.clone(), &all), 58 | BatchRead::new(key3.clone(), &none), 59 | BatchRead::new(key4.clone(), &none), 60 | ]; 61 | let mut results = client.batch_get(&bpolicy, batch).unwrap(); 62 | 63 | let result = results.remove(0); 64 | assert_eq!(result.key, key1); 65 | let record = result.record.unwrap(); 66 | assert_eq!(record.bins.keys().count(), 1); 67 | 68 | let result = results.remove(0); 69 | assert_eq!(result.key, key2); 70 | let record = result.record.unwrap(); 71 | assert_eq!(record.bins.keys().count(), 3); 72 | 73 | let result = results.remove(0); 74 | assert_eq!(result.key, key3); 75 | let record = result.record.unwrap(); 76 | assert_eq!(record.bins.keys().count(), 0); 77 | 78 | let result = results.remove(0); 79 | assert_eq!(result.key, key4); 80 | let record = result.record; 81 | assert!(record.is_none()); 82 | } 83 | -------------------------------------------------------------------------------- /src/commands/exists_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::{Cluster, Node}; 19 | use crate::commands::{buffer, Command, SingleCommand}; 20 | use crate::errors::{ErrorKind, Result}; 21 | use crate::net::Connection; 22 | use crate::policy::WritePolicy; 23 | use crate::{Key, ResultCode}; 24 | 25 | pub struct ExistsCommand<'a> { 26 | single_command: SingleCommand<'a>, 27 | policy: &'a WritePolicy, 28 | pub exists: bool, 29 | } 30 | 31 | impl<'a> ExistsCommand<'a> { 32 | pub fn new(policy: &'a WritePolicy, cluster: Arc, key: &'a Key) -> Self { 33 | ExistsCommand { 34 | single_command: SingleCommand::new(cluster, key), 35 | policy, 36 | exists: false, 37 | } 38 | } 39 | 40 | pub fn execute(&mut self) -> Result<()> { 41 | SingleCommand::execute(self.policy, self) 42 | } 43 | } 44 | 45 | impl<'a> Command for ExistsCommand<'a> { 46 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 47 | conn.buffer.write_timeout(timeout); 48 | Ok(()) 49 | } 50 | 51 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 52 | conn.flush() 53 | } 54 | 55 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 56 | conn.buffer.set_exists(self.policy, self.single_command.key) 57 | } 58 | 59 | fn get_node(&self) -> Result> { 60 | self.single_command.get_node() 61 | } 62 | 63 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 64 | // Read header. 65 | if let Err(err) = conn.read_buffer(buffer::MSG_TOTAL_HEADER_SIZE as usize) { 66 | warn!("Parse result error: {}", err); 67 | return Err(err); 68 | } 69 | 70 | conn.buffer.reset_offset()?; 71 | 72 | // A number of these are commented out because we just don't care enough to read 73 | // that section of the header. If we do care, uncomment and check! 74 | let result_code = ResultCode::from(conn.buffer.read_u8(Some(13))?); 75 | 76 | if result_code != ResultCode::Ok && result_code != ResultCode::KeyNotFoundError { 77 | bail!(ErrorKind::ServerError(result_code)); 78 | } 79 | 80 | self.exists = result_code == ResultCode::Ok; 81 | 82 | SingleCommand::empty_socket(conn) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/commands/delete_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::{Cluster, Node}; 19 | use crate::commands::{buffer, Command, SingleCommand}; 20 | use crate::errors::{ErrorKind, Result}; 21 | use crate::net::Connection; 22 | use crate::policy::WritePolicy; 23 | use crate::{Key, ResultCode}; 24 | 25 | pub struct DeleteCommand<'a> { 26 | single_command: SingleCommand<'a>, 27 | policy: &'a WritePolicy, 28 | pub existed: bool, 29 | } 30 | 31 | impl<'a> DeleteCommand<'a> { 32 | pub fn new(policy: &'a WritePolicy, cluster: Arc, key: &'a Key) -> Self { 33 | DeleteCommand { 34 | single_command: SingleCommand::new(cluster, key), 35 | policy, 36 | existed: false, 37 | } 38 | } 39 | 40 | pub fn execute(&mut self) -> Result<()> { 41 | SingleCommand::execute(self.policy, self) 42 | } 43 | } 44 | 45 | impl<'a> Command for DeleteCommand<'a> { 46 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 47 | conn.buffer.write_timeout(timeout); 48 | Ok(()) 49 | } 50 | 51 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 52 | conn.flush() 53 | } 54 | 55 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 56 | conn.buffer.set_delete(self.policy, self.single_command.key) 57 | } 58 | 59 | fn get_node(&self) -> Result> { 60 | self.single_command.get_node() 61 | } 62 | 63 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 64 | // Read header. 65 | if let Err(err) = conn.read_buffer(buffer::MSG_TOTAL_HEADER_SIZE as usize) { 66 | warn!("Parse result error: {}", err); 67 | return Err(err); 68 | } 69 | 70 | conn.buffer.reset_offset()?; 71 | 72 | // A number of these are commented out because we just don't care enough to read 73 | // that section of the header. If we do care, uncomment and check! 74 | let result_code = ResultCode::from(conn.buffer.read_u8(Some(13))?); 75 | 76 | if result_code != ResultCode::Ok && result_code != ResultCode::KeyNotFoundError { 77 | bail!(ErrorKind::ServerError(result_code)); 78 | } 79 | 80 | self.existed = result_code == ResultCode::Ok; 81 | 82 | SingleCommand::empty_socket(conn) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/commands/write_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::time::Duration; 17 | 18 | use crate::cluster::{Cluster, Node}; 19 | use crate::commands::buffer; 20 | use crate::commands::{Command, SingleCommand}; 21 | use crate::errors::{ErrorKind, Result}; 22 | use crate::net::Connection; 23 | use crate::operations::OperationType; 24 | use crate::policy::WritePolicy; 25 | use crate::{Bin, Key, ResultCode}; 26 | 27 | pub struct WriteCommand<'a, A: 'a> { 28 | single_command: SingleCommand<'a>, 29 | policy: &'a WritePolicy, 30 | bins: &'a [A], 31 | operation: OperationType, 32 | } 33 | 34 | impl<'a, 'b, A: AsRef>> WriteCommand<'a, A> { 35 | pub fn new( 36 | policy: &'a WritePolicy, 37 | cluster: Arc, 38 | key: &'a Key, 39 | bins: &'a [A], 40 | operation: OperationType, 41 | ) -> Self { 42 | WriteCommand { 43 | single_command: SingleCommand::new(cluster, key), 44 | bins, 45 | policy, 46 | operation, 47 | } 48 | } 49 | 50 | pub fn execute(&mut self) -> Result<()> { 51 | SingleCommand::execute(self.policy, self) 52 | } 53 | } 54 | 55 | impl<'a, 'b, A: AsRef>> Command for WriteCommand<'a, A> { 56 | fn write_timeout(&mut self, conn: &mut Connection, timeout: Option) -> Result<()> { 57 | conn.buffer.write_timeout(timeout); 58 | Ok(()) 59 | } 60 | 61 | fn write_buffer(&mut self, conn: &mut Connection) -> Result<()> { 62 | conn.flush() 63 | } 64 | 65 | fn prepare_buffer(&mut self, conn: &mut Connection) -> Result<()> { 66 | conn.buffer.set_write( 67 | self.policy, 68 | self.operation, 69 | self.single_command.key, 70 | self.bins, 71 | ) 72 | } 73 | 74 | fn get_node(&self) -> Result> { 75 | self.single_command.get_node() 76 | } 77 | 78 | fn parse_result(&mut self, conn: &mut Connection) -> Result<()> { 79 | // Read header. 80 | if let Err(err) = conn.read_buffer(buffer::MSG_TOTAL_HEADER_SIZE as usize) { 81 | warn!("Parse result error: {}", err); 82 | return Err(err); 83 | } 84 | 85 | conn.buffer.reset_offset()?; 86 | 87 | let result_code = ResultCode::from(conn.buffer.read_u8(Some(13))?); 88 | if result_code != ResultCode::Ok { 89 | bail!(ErrorKind::ServerError(result_code)); 90 | } 91 | 92 | SingleCommand::empty_socket(conn) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/policy/batch_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::expressions::FilterExpression; 17 | use crate::policy::{BasePolicy, Concurrency, PolicyLike}; 18 | 19 | /// `BatchPolicy` encapsulates parameters for all batch operations. 20 | #[derive(Debug, Clone)] 21 | pub struct BatchPolicy { 22 | /// Base policy instance 23 | pub base_policy: BasePolicy, 24 | 25 | /// Concurrency mode for batch requests: Sequential or Parallel (with optional max. no of 26 | /// parallel threads). 27 | pub concurrency: Concurrency, 28 | 29 | /// Allow batch to be processed immediately in the server's receiving thread when the server 30 | /// deems it to be appropriate. If false, the batch will always be processed in separate 31 | /// transaction threads. 32 | /// 33 | /// For batch exists or batch reads of smaller sized records (<= 1K per record), inline 34 | /// processing will be significantly faster on "in memory" namespaces. The server disables 35 | /// inline processing on disk based namespaces regardless of this policy field. 36 | /// 37 | /// Inline processing can introduce the possibility of unfairness because the server can 38 | /// process the entire batch before moving onto the next command. 39 | /// 40 | /// Default: true 41 | pub allow_inline: bool, 42 | 43 | /// Send set name field to server for every key in the batch. This is only necessary when 44 | /// authentication is enabled and security roles are defined on a per-set basis. 45 | /// 46 | /// Default: false 47 | pub send_set_name: bool, 48 | 49 | /// Optional Filter Expression 50 | pub filter_expression: Option, 51 | } 52 | 53 | impl BatchPolicy { 54 | /// Create a new batch policy instance. 55 | pub fn new() -> Self { 56 | BatchPolicy::default() 57 | } 58 | 59 | /// Get the current Filter Expression 60 | pub const fn filter_expression(&self) -> &Option { 61 | &self.filter_expression 62 | } 63 | } 64 | 65 | impl Default for BatchPolicy { 66 | fn default() -> Self { 67 | BatchPolicy { 68 | base_policy: BasePolicy::default(), 69 | concurrency: Concurrency::Sequential, 70 | allow_inline: true, 71 | send_set_name: false, 72 | filter_expression: None, 73 | } 74 | } 75 | } 76 | 77 | impl PolicyLike for BatchPolicy { 78 | fn base(&self) -> &BasePolicy { 79 | &self.base_policy 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/src/udf.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::common; 17 | use env_logger; 18 | 19 | use aerospike::Task; 20 | use aerospike::*; 21 | 22 | #[test] 23 | fn execute_udf() { 24 | let _ = env_logger::try_init(); 25 | 26 | let client = common::client(); 27 | let namespace = common::namespace(); 28 | let set_name = &common::rand_str(10); 29 | 30 | let wpolicy = WritePolicy::default(); 31 | let key = as_key!(namespace, set_name, 1); 32 | let wbin = as_bin!("bin", 10); 33 | let bins = vec![&wbin]; 34 | client.put(&wpolicy, &key, &bins).unwrap(); 35 | 36 | let udf_body1 = r#" 37 | function func_div(rec, div) 38 | local ret = map() 39 | local x = rec['bin'] 40 | rec['bin2'] = math.floor(x / div) 41 | aerospike:update(rec) 42 | ret['status'] = 'OK' 43 | ret['res'] = math.floor(x / div) 44 | return ret 45 | end 46 | "#; 47 | 48 | let udf_body2 = r#" 49 | function echo(rec, val) 50 | return val 51 | end 52 | "#; 53 | 54 | let task = client 55 | .register_udf( 56 | &wpolicy, 57 | udf_body1.as_bytes(), 58 | "test_udf1.lua", 59 | UDFLang::Lua, 60 | ) 61 | .unwrap(); 62 | task.wait_till_complete(None).unwrap(); 63 | 64 | let task = client 65 | .register_udf( 66 | &wpolicy, 67 | udf_body2.as_bytes(), 68 | "test_udf2.lua", 69 | UDFLang::Lua, 70 | ) 71 | .unwrap(); 72 | task.wait_till_complete(None).unwrap(); 73 | 74 | let res = client.execute_udf( 75 | &wpolicy, 76 | &key, 77 | "test_udf2", 78 | "echo", 79 | Some(&[as_val!("ha ha...")]), 80 | ); 81 | assert_eq!(Some(as_val!("ha ha...")), res.unwrap()); 82 | 83 | let res = client.execute_udf(&wpolicy, &key, "test_udf1", "func_div", Some(&[as_val!(2)])); 84 | if let Ok(Some(Value::HashMap(values))) = res { 85 | assert_eq!(values.get(&as_val!("status")), Some(&as_val!("OK"))); 86 | assert_eq!(values.get(&as_val!("res")), Some(&as_val!(5))); 87 | } else { 88 | panic!("UDF function did not return expected value"); 89 | } 90 | 91 | let res = client.execute_udf(&wpolicy, &key, "test_udf1", "no_such_function", None); 92 | if let Err(Error(ErrorKind::UdfBadResponse(response), _)) = res { 93 | assert_eq!(response, "function not found".to_string()); 94 | } else { 95 | panic!("UDF function did not return the expected error"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/cluster/partition_tokenizer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::collections::hash_map::Entry::{Occupied, Vacant}; 16 | use std::collections::HashMap; 17 | use std::str; 18 | use std::sync::Arc; 19 | use std::vec::Vec; 20 | 21 | use parking_lot::RwLock; 22 | 23 | use crate::cluster::node; 24 | use crate::cluster::Node; 25 | use crate::commands::Message; 26 | use crate::errors::{ErrorKind, Result}; 27 | use crate::net::Connection; 28 | 29 | const REPLICAS_NAME: &str = "replicas-master"; 30 | 31 | // Validates a Database server node 32 | #[derive(Debug, Clone)] 33 | pub struct PartitionTokenizer { 34 | buffer: Vec, 35 | } 36 | 37 | impl PartitionTokenizer { 38 | pub fn new(conn: &mut Connection) -> Result { 39 | let info_map = Message::info(conn, &[REPLICAS_NAME])?; 40 | if let Some(buf) = info_map.get(REPLICAS_NAME) { 41 | return Ok(PartitionTokenizer { 42 | buffer: buf.as_bytes().to_owned(), 43 | }); 44 | } 45 | bail!(ErrorKind::BadResponse("Missing replicas info".to_string())); 46 | } 47 | 48 | pub fn update_partition( 49 | &self, 50 | nmap: Arc>>>>, 51 | node: Arc, 52 | ) -> Result>>> { 53 | let mut amap = nmap.read().clone(); 54 | 55 | // :;:; ... 56 | let part_str = str::from_utf8(&self.buffer)?; 57 | let mut parts = part_str.trim_end().split(|c| c == ':' || c == ';'); 58 | loop { 59 | match (parts.next(), parts.next()) { 60 | (Some(ns), Some(part)) => { 61 | let restore_buffer = base64::decode(part)?; 62 | match amap.entry(ns.to_string()) { 63 | Vacant(entry) => { 64 | entry.insert(vec![node.clone(); node::PARTITIONS]); 65 | } 66 | Occupied(mut entry) => { 67 | for (idx, item) in entry.get_mut().iter_mut().enumerate() { 68 | if restore_buffer[idx >> 3] & (0x80 >> (idx & 7) as u8) != 0 { 69 | *item = node.clone(); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | (None, None) => break, 76 | _ => bail!(ErrorKind::BadResponse( 77 | "Error parsing partition info".to_string() 78 | )), 79 | } 80 | } 81 | 82 | Ok(amap) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/policy/scan_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::expressions::FilterExpression; 17 | use crate::policy::{BasePolicy, PolicyLike}; 18 | 19 | /// `ScanPolicy` encapsulates optional parameters used in scan operations. 20 | #[derive(Debug, Clone)] 21 | pub struct ScanPolicy { 22 | /// Base policy instance 23 | pub base_policy: BasePolicy, 24 | 25 | /// Percent of data to scan. Valid integer range is 1 to 100. Default is 100. 26 | /// This is deprected and won't be sent to the server. 27 | pub scan_percent: u8, 28 | 29 | /// Maximum number of concurrent requests to server nodes at any point in time. If there are 16 30 | /// nodes in the cluster and `max_concurrent_nodes` is 8, then scan requests will be made to 8 31 | /// nodes in parallel. When a scan completes, a new scan request will be issued until all 16 32 | /// nodes have been scanned. Default (0) is to issue requests to all server nodes in parallel. 33 | pub max_concurrent_nodes: usize, 34 | 35 | /// Number of records to place in queue before blocking. Records received from multiple server 36 | /// nodes will be placed in a queue. A separate thread consumes these records in parallel. If 37 | /// the queue is full, the producer threads will block until records are consumed. 38 | pub record_queue_size: usize, 39 | 40 | /// Terminate scan if cluster is in fluctuating state. 41 | /// This is deprected and won't be sent to the server. 42 | pub fail_on_cluster_change: bool, 43 | 44 | /// Maximum time in milliseconds to wait when polling socket for availability prior to 45 | /// performing an operation on the socket on the server side. Zero means there is no socket 46 | /// timeout. Default: 10,000 ms. 47 | pub socket_timeout: u32, 48 | 49 | /// Optional Filter Expression 50 | pub filter_expression: Option, 51 | } 52 | 53 | impl ScanPolicy { 54 | /// Create a new scan policy instance with default parameters. 55 | pub fn new() -> Self { 56 | ScanPolicy::default() 57 | } 58 | 59 | /// Get the current Filter Expression 60 | pub const fn filter_expression(&self) -> &Option { 61 | &self.filter_expression 62 | } 63 | } 64 | 65 | impl Default for ScanPolicy { 66 | fn default() -> Self { 67 | ScanPolicy { 68 | base_policy: BasePolicy::default(), 69 | scan_percent: 100, 70 | max_concurrent_nodes: 0, 71 | record_queue_size: 1024, 72 | fail_on_cluster_change: true, 73 | socket_timeout: 10000, 74 | filter_expression: None, 75 | } 76 | } 77 | } 78 | 79 | impl PolicyLike for ScanPolicy { 80 | fn base(&self) -> &BasePolicy { 81 | &self.base_policy 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/src/task.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::common; 17 | use aerospike::errors::ErrorKind; 18 | use aerospike::task::{Status, Task}; 19 | use aerospike::*; 20 | use std::thread; 21 | use std::time::Duration; 22 | 23 | // If registering udf is successful, querying RegisterTask will return Status::Complete 24 | // If udf does not exist, querying RegisterTask will return error 25 | #[test] 26 | fn register_task_test() { 27 | let client = common::client(); 28 | 29 | let code = r#" 30 | local function putBin(r,name,value) 31 | if not aerospike:exists(r) then aerospike:create(r) end 32 | r[name] = value 33 | aerospike:update(r) 34 | end 35 | function writeBin(r,name,value) 36 | putBin(r,name,value) 37 | end 38 | "#; 39 | 40 | let udf_name = common::rand_str(10); 41 | let udf_file_name = udf_name.clone().to_owned() + ".LUA"; 42 | 43 | let register_task = client 44 | .register_udf( 45 | &WritePolicy::default(), 46 | code.as_bytes(), 47 | &udf_file_name, 48 | UDFLang::Lua, 49 | ) 50 | .unwrap(); 51 | 52 | assert!(matches!( 53 | register_task.wait_till_complete(None), 54 | Ok(Status::Complete) 55 | )); 56 | 57 | client 58 | .remove_udf(&WritePolicy::default(), &udf_name, UDFLang::Lua) 59 | .unwrap(); 60 | // Wait for some time to ensure UDF has been unregistered on all nodes. 61 | thread::sleep(Duration::from_secs(2)); 62 | 63 | let timeout = Duration::from_millis(100); 64 | assert!(matches!( 65 | register_task.wait_till_complete(Some(timeout)), 66 | Err(Error(ErrorKind::Timeout(_), _)) 67 | )); 68 | } 69 | 70 | // If creating index is successful, querying IndexTask will return Status::Complete 71 | #[test] 72 | fn index_task_test() { 73 | let client = common::client(); 74 | let namespace = common::namespace(); 75 | let set_name = common::rand_str(10); 76 | let bin_name = common::rand_str(10); 77 | let index_name = common::rand_str(10); 78 | 79 | let wpolicy = WritePolicy::default(); 80 | for i in 0..2 as i64 { 81 | let key = as_key!(namespace, &set_name, i); 82 | let wbin = as_bin!(&bin_name, i); 83 | let bins = vec![&wbin]; 84 | client.put(&wpolicy, &key, &bins).unwrap(); 85 | } 86 | 87 | let index_task = client 88 | .create_index( 89 | &wpolicy, 90 | &namespace, 91 | &set_name, 92 | &bin_name, 93 | &index_name, 94 | IndexType::Numeric, 95 | ) 96 | .unwrap(); 97 | 98 | assert!(matches!( 99 | index_task.wait_till_complete(None), 100 | Ok(Status::Complete) 101 | )); 102 | } 103 | -------------------------------------------------------------------------------- /src/net/host.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::fmt; 17 | use std::io; 18 | use std::net::{SocketAddr, ToSocketAddrs}; 19 | use std::vec::IntoIter; 20 | 21 | use crate::errors::{ErrorKind, Result, ResultExt}; 22 | use crate::net::parser::Parser; 23 | 24 | /// Host name/port of database server. 25 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 26 | pub struct Host { 27 | /// Host name or IP address of database server. 28 | pub name: String, 29 | 30 | /// Port of database server. 31 | pub port: u16, 32 | } 33 | 34 | impl Host { 35 | /// Create a new host instance given a hostname/IP and a port number. 36 | pub fn new(name: &str, port: u16) -> Self { 37 | Host { 38 | name: name.to_string(), 39 | port, 40 | } 41 | } 42 | 43 | /// Returns a string representation of the host's address. 44 | pub fn address(&self) -> String { 45 | format!("{}:{}", self.name, self.port) 46 | } 47 | } 48 | 49 | impl ToSocketAddrs for Host { 50 | type Iter = IntoIter; 51 | fn to_socket_addrs(&self) -> io::Result> { 52 | (self.name.as_str(), self.port).to_socket_addrs() 53 | } 54 | } 55 | 56 | impl fmt::Display for Host { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | write!(f, "{}:{}", self.name, self.port) 59 | } 60 | } 61 | 62 | /// A trait for objects which can be converted to one or more `Host` values. 63 | pub trait ToHosts { 64 | /// Converts this object into a list of `Host`s. 65 | /// 66 | /// # Errors 67 | /// 68 | /// Any errors encountered during conversion will be returned as an `Err`. 69 | fn to_hosts(&self) -> Result>; 70 | } 71 | 72 | impl ToHosts for Vec { 73 | fn to_hosts(&self) -> Result> { 74 | Ok(self.clone()) 75 | } 76 | } 77 | 78 | impl ToHosts for String { 79 | fn to_hosts(&self) -> Result> { 80 | let mut parser = Parser::new(self, 3000); 81 | parser 82 | .read_hosts() 83 | .chain_err(|| ErrorKind::InvalidArgument(format!("Invalid hosts list: '{}'", self))) 84 | } 85 | } 86 | 87 | impl<'a> ToHosts for &'a str { 88 | fn to_hosts(&self) -> Result> { 89 | (*self).to_string().to_hosts() 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::{Host, ToHosts}; 96 | 97 | #[test] 98 | fn to_hosts() { 99 | assert_eq!( 100 | vec![Host::new("foo", 3000)], 101 | String::from("foo").to_hosts().unwrap() 102 | ); 103 | assert_eq!(vec![Host::new("foo", 3000)], "foo".to_hosts().unwrap()); 104 | assert_eq!(vec![Host::new("foo", 1234)], "foo:1234".to_hosts().unwrap()); 105 | assert_eq!( 106 | vec![Host::new("foo", 1234), Host::new("bar", 1234)], 107 | "foo:1234,bar:1234".to_hosts().unwrap() 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/operations/scalar.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | //! String/number bin operations. Create operations used by the client's `operate()` method. 17 | 18 | use crate::operations::cdt_context::DEFAULT_CTX; 19 | use crate::operations::{Operation, OperationBin, OperationData, OperationType}; 20 | use crate::Bin; 21 | 22 | /// Create read all record bins database operation. 23 | pub const fn get<'a>() -> Operation<'a> { 24 | Operation { 25 | op: OperationType::Read, 26 | ctx: DEFAULT_CTX, 27 | bin: OperationBin::All, 28 | data: OperationData::None, 29 | } 30 | } 31 | 32 | /// Create read record header database operation. 33 | pub const fn get_header<'a>() -> Operation<'a> { 34 | Operation { 35 | op: OperationType::Read, 36 | ctx: DEFAULT_CTX, 37 | bin: OperationBin::None, 38 | data: OperationData::None, 39 | } 40 | } 41 | 42 | /// Create read bin database operation. 43 | pub const fn get_bin(bin_name: &str) -> Operation<'_> { 44 | Operation { 45 | op: OperationType::Read, 46 | ctx: DEFAULT_CTX, 47 | bin: OperationBin::Name(bin_name), 48 | data: OperationData::None, 49 | } 50 | } 51 | 52 | /// Create set database operation. 53 | pub const fn put<'a>(bin: &'a Bin) -> Operation<'a> { 54 | Operation { 55 | op: OperationType::Write, 56 | ctx: DEFAULT_CTX, 57 | bin: OperationBin::Name(bin.name), 58 | data: OperationData::Value(&bin.value), 59 | } 60 | } 61 | 62 | /// Create string append database operation. 63 | pub const fn append<'a>(bin: &'a Bin) -> Operation<'a> { 64 | Operation { 65 | op: OperationType::Append, 66 | ctx: DEFAULT_CTX, 67 | bin: OperationBin::Name(bin.name), 68 | data: OperationData::Value(&bin.value), 69 | } 70 | } 71 | 72 | /// Create string prepend database operation. 73 | pub const fn prepend<'a>(bin: &'a Bin) -> Operation<'a> { 74 | Operation { 75 | op: OperationType::Prepend, 76 | ctx: DEFAULT_CTX, 77 | bin: OperationBin::Name(bin.name), 78 | data: OperationData::Value(&bin.value), 79 | } 80 | } 81 | 82 | /// Create integer add database operation. 83 | pub const fn add<'a>(bin: &'a Bin) -> Operation<'a> { 84 | Operation { 85 | op: OperationType::Incr, 86 | ctx: DEFAULT_CTX, 87 | bin: OperationBin::Name(bin.name), 88 | data: OperationData::Value(&bin.value), 89 | } 90 | } 91 | 92 | /// Create touch database operation. 93 | pub const fn touch<'a>() -> Operation<'a> { 94 | Operation { 95 | op: OperationType::Touch, 96 | ctx: DEFAULT_CTX, 97 | bin: OperationBin::None, 98 | data: OperationData::None, 99 | } 100 | } 101 | 102 | /// Create delete database operation 103 | pub const fn delete<'a>() -> Operation<'a> { 104 | Operation { 105 | op: OperationType::Delete, 106 | ctx: DEFAULT_CTX, 107 | bin: OperationBin::None, 108 | data: OperationData::None, 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/commands/info_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; 17 | use std::collections::HashMap; 18 | use std::io::{Cursor, Write}; 19 | use std::str; 20 | 21 | use crate::errors::Result; 22 | use crate::net::Connection; 23 | 24 | // MAX_BUFFER_SIZE protects against allocating massive memory blocks 25 | // for buffers. 26 | const MAX_BUFFER_SIZE: usize = 1024 * 1024 + 8; // 1 MB + header 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct Message { 30 | buf: Vec, 31 | } 32 | 33 | impl Message { 34 | pub fn info(conn: &mut Connection, commands: &[&str]) -> Result> { 35 | let cmd = commands.join("\n") + "\n"; 36 | let mut msg = Message::new(&cmd.into_bytes())?; 37 | 38 | msg.send(conn)?; 39 | Ok(msg.parse_response()?) 40 | } 41 | 42 | fn new(data: &[u8]) -> Result { 43 | let mut len = Vec::with_capacity(8); 44 | len.write_u64::(data.len() as u64).unwrap(); 45 | 46 | let mut buf: Vec = Vec::with_capacity(1024); 47 | buf.push(2); // version 48 | buf.push(1); // msg_type 49 | buf.write_all(&len[2..8])?; 50 | buf.write_all(data)?; 51 | 52 | Ok(Message { buf }) 53 | } 54 | 55 | fn data_len(&self) -> u64 { 56 | let mut lbuf: Vec = vec![0; 8]; 57 | lbuf[2..8].clone_from_slice(&self.buf[2..8]); 58 | let mut rdr = Cursor::new(lbuf); 59 | rdr.read_u64::().unwrap() 60 | } 61 | 62 | fn send(&mut self, conn: &mut Connection) -> Result<()> { 63 | conn.write(&self.buf)?; 64 | 65 | // read the header 66 | conn.read(self.buf[..8].as_mut())?; 67 | 68 | // figure our message size and grow the buffer if necessary 69 | let data_len = self.data_len() as usize; 70 | 71 | // Corrupted data streams can result in a huge length. 72 | // Do a sanity check here. 73 | if data_len > MAX_BUFFER_SIZE { 74 | bail!("Invalid size for info command buffer: {}", data_len); 75 | } 76 | self.buf.resize(data_len, 0); 77 | 78 | // read the message content 79 | conn.read(self.buf.as_mut())?; 80 | 81 | Ok(()) 82 | } 83 | 84 | fn parse_response(&self) -> Result> { 85 | let response = str::from_utf8(&self.buf)?; 86 | let response = response.trim_matches('\n'); 87 | 88 | debug!("response from server for info command: {:?}", response); 89 | let mut result: HashMap = HashMap::new(); 90 | 91 | for tuple in response.split('\n') { 92 | let mut kv = tuple.split('\t'); 93 | let key = kv.next(); 94 | let val = kv.next(); 95 | 96 | match (key, val) { 97 | (Some(key), Some(val)) => result.insert(key.to_string(), val.to_string()), 98 | (Some(key), None) => result.insert(key.to_string(), "".to_string()), 99 | _ => bail!("Parsing Info command failed"), 100 | }; 101 | } 102 | 103 | Ok(result) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/query/recordset.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache Licenseersion 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | extern crate rand; 17 | 18 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 19 | use std::thread; 20 | 21 | use crossbeam_queue::SegQueue; 22 | use rand::Rng; 23 | 24 | use crate::errors::Result; 25 | use crate::Record; 26 | 27 | /// Virtual collection of records retrieved through queries and scans. During a query/scan, 28 | /// multiple threads will retrieve records from the server nodes and put these records on an 29 | /// internal queue managed by the recordset. The single user thread consumes these records from the 30 | /// queue. 31 | pub struct Recordset { 32 | instances: AtomicUsize, 33 | record_queue_count: AtomicUsize, 34 | record_queue_size: AtomicUsize, 35 | record_queue: SegQueue>, 36 | active: AtomicBool, 37 | task_id: AtomicUsize, 38 | } 39 | 40 | impl Recordset { 41 | #[doc(hidden)] 42 | pub fn new(rec_queue_size: usize, nodes: usize) -> Self { 43 | let mut rng = rand::thread_rng(); 44 | let task_id = rng.gen::(); 45 | 46 | Recordset { 47 | instances: AtomicUsize::new(nodes), 48 | record_queue_size: AtomicUsize::new(rec_queue_size), 49 | record_queue_count: AtomicUsize::new(0), 50 | record_queue: SegQueue::new(), 51 | active: AtomicBool::new(true), 52 | task_id: AtomicUsize::new(task_id), 53 | } 54 | } 55 | 56 | /// Close the query. 57 | pub fn close(&self) { 58 | self.active.store(false, Ordering::Relaxed) 59 | } 60 | 61 | /// Check whether the query is still active. 62 | pub fn is_active(&self) -> bool { 63 | self.active.load(Ordering::Relaxed) 64 | } 65 | 66 | #[doc(hidden)] 67 | pub fn push(&self, record: Result) -> Option> { 68 | if self.record_queue_count.fetch_add(1, Ordering::Relaxed) 69 | < self.record_queue_size.load(Ordering::Relaxed) 70 | { 71 | self.record_queue.push(record); 72 | return None; 73 | } 74 | self.record_queue_count.fetch_sub(1, Ordering::Relaxed); 75 | Some(record) 76 | } 77 | 78 | /// Returns the task ID for the scan/query. 79 | pub fn task_id(&self) -> u64 { 80 | self.task_id.load(Ordering::Relaxed) as u64 81 | } 82 | 83 | #[doc(hidden)] 84 | pub fn signal_end(&self) { 85 | if self.instances.fetch_sub(1, Ordering::Relaxed) == 1 { 86 | self.close() 87 | }; 88 | } 89 | } 90 | 91 | impl<'a> Iterator for &'a Recordset { 92 | type Item = Result; 93 | 94 | fn next(&mut self) -> Option> { 95 | loop { 96 | if self.is_active() || !self.record_queue.is_empty() { 97 | let result = self.record_queue.pop().ok(); 98 | if result.is_some() { 99 | self.record_queue_count.fetch_sub(1, Ordering::Relaxed); 100 | return result; 101 | } 102 | thread::yield_now(); 103 | continue; 104 | } else { 105 | return None; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tools/benchmark/src/generator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use aerospike::Key; 17 | 18 | #[derive(Debug)] 19 | pub struct KeyPartitions { 20 | namespace: String, 21 | set: String, 22 | index: i64, 23 | end: i64, 24 | keys_per_partition: i64, 25 | remainder: i64, 26 | } 27 | 28 | impl KeyPartitions { 29 | pub fn new( 30 | namespace: String, 31 | set: String, 32 | start_key: i64, 33 | count: i64, 34 | partitions: i64, 35 | ) -> Self { 36 | KeyPartitions { 37 | namespace: namespace, 38 | set: set, 39 | index: start_key, 40 | end: start_key + count, 41 | keys_per_partition: count / partitions, 42 | remainder: count % partitions, 43 | } 44 | } 45 | } 46 | 47 | impl Iterator for KeyPartitions { 48 | type Item = KeyRange; 49 | 50 | fn next(&mut self) -> Option { 51 | if self.index < self.end { 52 | let mut count = self.keys_per_partition; 53 | if self.remainder > 0 { 54 | count += 1; 55 | self.remainder -= 1; 56 | } 57 | let range = KeyRange::new(self.namespace.clone(), self.set.clone(), self.index, count); 58 | self.index += count; 59 | Some(range) 60 | } else { 61 | None 62 | } 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct KeyRange { 68 | namespace: String, 69 | set: String, 70 | index: i64, 71 | end: i64, 72 | } 73 | 74 | impl KeyRange { 75 | pub fn new(namespace: String, set: String, start: i64, count: i64) -> Self { 76 | KeyRange { 77 | namespace: namespace, 78 | set: set, 79 | index: start, 80 | end: start + count, 81 | } 82 | } 83 | } 84 | 85 | impl Iterator for KeyRange { 86 | type Item = Key; 87 | 88 | fn next(&mut self) -> Option { 89 | if self.index < self.end { 90 | let key = as_key!(self.namespace.clone(), self.set.clone(), self.index); 91 | self.index += 1; 92 | Some(key) 93 | } else { 94 | None 95 | } 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod test { 101 | use super::*; 102 | 103 | #[test] 104 | fn test_key_range() { 105 | let mut range = KeyRange::new("foo".into(), "bar".into(), 0, 3); 106 | assert_eq!(range.next(), Some(as_key!("foo", "bar", 0))); 107 | assert_eq!(range.next(), Some(as_key!("foo", "bar", 1))); 108 | assert_eq!(range.next(), Some(as_key!("foo", "bar", 2))); 109 | assert!(range.next().is_none()); 110 | } 111 | 112 | #[test] 113 | fn test_key_partitions() { 114 | let partitions = KeyPartitions::new("foo".into(), "bar".into(), 0, 10, 3); 115 | let mut parts = 0; 116 | let mut keys = 0; 117 | for part in partitions { 118 | for key in part { 119 | assert_eq!(key, as_key!("foo", "bar", keys)); 120 | keys += 1; 121 | } 122 | parts += 1 123 | } 124 | assert_eq!(parts, 3); 125 | assert_eq!(keys, 10); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/src/kv.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | use aerospike::operations; 16 | use aerospike::{ 17 | as_bin, as_blob, as_geo, as_key, as_list, as_map, as_val, Bins, ReadPolicy, Value, WritePolicy, 18 | }; 19 | use env_logger; 20 | 21 | use crate::common; 22 | 23 | #[test] 24 | fn connect() { 25 | let _ = env_logger::try_init(); 26 | 27 | let client = common::client(); 28 | let namespace: &str = common::namespace(); 29 | let set_name = &common::rand_str(10); 30 | let policy = ReadPolicy::default(); 31 | let wpolicy = WritePolicy::default(); 32 | let key = as_key!(namespace, set_name, -1); 33 | 34 | client.delete(&wpolicy, &key).unwrap(); 35 | 36 | let bins = [ 37 | as_bin!("bin999", "test string"), 38 | as_bin!("bin bool", true), 39 | as_bin!("bin vec![int]", as_list![1u32, 2u32, 3u32]), 40 | as_bin!("bin vec![u8]", as_blob!(vec![1u8, 2u8, 3u8])), 41 | as_bin!("bin map", as_map!(1 => 1, 2 => 2, 3 => "hi!", 4 => false)), 42 | as_bin!("bin f64", 1.64f64), 43 | as_bin!("bin Nil", None), // Writing None erases the bin! 44 | as_bin!( 45 | "bin Geo", 46 | as_geo!(format!( 47 | r#"{{ "type": "Point", "coordinates": [{}, {}] }}"#, 48 | 17.119_381, 19.45612 49 | )) 50 | ), 51 | as_bin!("bin-name-len-15", "max. bin name length is 15 chars"), 52 | ]; 53 | client.put(&wpolicy, &key, &bins).unwrap(); 54 | 55 | let record = client.get(&policy, &key, Bins::All).unwrap(); 56 | let bins = record.bins; 57 | assert_eq!(bins.len(), 8); 58 | assert_eq!(bins.get("bin bool"), Some(&Value::Bool(true))); 59 | assert_eq!(bins.get("bin999"), Some(&Value::from("test string"))); 60 | assert_eq!(bins.get("bin vec![int]"), Some(&as_list![1u32, 2u32, 3u32])); 61 | assert_eq!( 62 | bins.get("bin vec![u8]"), 63 | Some(&as_blob!(vec![1u8, 2u8, 3u8])) 64 | ); 65 | assert_eq!( 66 | bins.get("bin map"), 67 | Some(&as_map!(1 => 1, 2 => 2, 3 => "hi!", 4 => false)) 68 | ); 69 | assert_eq!(bins.get("bin f64"), Some(&Value::from(1.64f64))); 70 | assert_eq!( 71 | bins.get("bin Geo"), 72 | Some(&as_geo!( 73 | r#"{ "type": "Point", "coordinates": [17.119381, 19.45612] }"# 74 | )) 75 | ); 76 | assert_eq!( 77 | bins.get("bin-name-len-15"), 78 | Some(&Value::from("max. bin name length is 15 chars")) 79 | ); 80 | 81 | client.touch(&wpolicy, &key).unwrap(); 82 | 83 | let bins = Bins::from(["bin999", "bin f64"]); 84 | let record = client.get(&policy, &key, bins).unwrap(); 85 | assert_eq!(record.bins.len(), 2); 86 | 87 | let record = client.get(&policy, &key, Bins::None).unwrap(); 88 | assert_eq!(record.bins.len(), 0); 89 | 90 | let exists = client.exists(&wpolicy, &key).unwrap(); 91 | assert!(exists); 92 | 93 | let bin = as_bin!("bin999", "test string"); 94 | let ops = &vec![operations::put(&bin), operations::get()]; 95 | client.operate(&wpolicy, &key, ops).unwrap(); 96 | 97 | let existed = client.delete(&wpolicy, &key).unwrap(); 98 | assert!(existed); 99 | 100 | let existed = client.delete(&wpolicy, &key).unwrap(); 101 | assert!(!existed); 102 | } 103 | -------------------------------------------------------------------------------- /tests/src/scan.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::sync::atomic::{AtomicUsize, Ordering}; 17 | use std::sync::Arc; 18 | use std::thread; 19 | 20 | use crate::common; 21 | use env_logger; 22 | 23 | use aerospike::*; 24 | 25 | const EXPECTED: usize = 1000; 26 | 27 | fn create_test_set(no_records: usize) -> String { 28 | let client = common::client(); 29 | let namespace = common::namespace(); 30 | let set_name = common::rand_str(10); 31 | 32 | let wpolicy = WritePolicy::default(); 33 | for i in 0..no_records as i64 { 34 | let key = as_key!(namespace, &set_name, i); 35 | let wbin = as_bin!("bin", i); 36 | let bins = vec![&wbin]; 37 | client.delete(&wpolicy, &key).unwrap(); 38 | client.put(&wpolicy, &key, &bins).unwrap(); 39 | } 40 | 41 | set_name 42 | } 43 | 44 | #[test] 45 | fn scan_single_consumer() { 46 | let _ = env_logger::try_init(); 47 | 48 | let client = common::client(); 49 | let namespace = common::namespace(); 50 | let set_name = create_test_set(EXPECTED); 51 | 52 | let spolicy = ScanPolicy::default(); 53 | let rs = client 54 | .scan(&spolicy, namespace, &set_name, Bins::All) 55 | .unwrap(); 56 | 57 | let count = (&*rs).filter(Result::is_ok).count(); 58 | assert_eq!(count, EXPECTED); 59 | } 60 | 61 | #[test] 62 | fn scan_multi_consumer() { 63 | let _ = env_logger::try_init(); 64 | 65 | let client = common::client(); 66 | let namespace = common::namespace(); 67 | let set_name = create_test_set(EXPECTED); 68 | 69 | let mut spolicy = ScanPolicy::default(); 70 | spolicy.record_queue_size = 4096; 71 | let rs = client 72 | .scan(&spolicy, namespace, &set_name, Bins::All) 73 | .unwrap(); 74 | 75 | let count = Arc::new(AtomicUsize::new(0)); 76 | let mut threads = vec![]; 77 | 78 | for _ in 0..8 { 79 | let count = count.clone(); 80 | let rs = rs.clone(); 81 | threads.push(thread::spawn(move || { 82 | let ok = (&*rs).filter(Result::is_ok).count(); 83 | count.fetch_add(ok, Ordering::Relaxed); 84 | })); 85 | } 86 | 87 | for t in threads { 88 | t.join().expect("Cannot join thread"); 89 | } 90 | 91 | assert_eq!(count.load(Ordering::Relaxed), EXPECTED); 92 | } 93 | 94 | #[test] 95 | fn scan_node() { 96 | let _ = env_logger::try_init(); 97 | 98 | let client = Arc::new(common::client()); 99 | let namespace = common::namespace(); 100 | let set_name = create_test_set(EXPECTED); 101 | 102 | let count = Arc::new(AtomicUsize::new(0)); 103 | let mut threads = vec![]; 104 | 105 | for node in client.nodes() { 106 | let client = client.clone(); 107 | let count = count.clone(); 108 | let set_name = set_name.clone(); 109 | threads.push(thread::spawn(move || { 110 | let spolicy = ScanPolicy::default(); 111 | let rs = client 112 | .scan_node(&spolicy, node, namespace, &set_name, Bins::All) 113 | .unwrap(); 114 | let ok = (&*rs).filter(Result::is_ok).count(); 115 | count.fetch_add(ok, Ordering::Relaxed); 116 | })); 117 | } 118 | 119 | for t in threads { 120 | t.join().expect("Cannot join thread"); 121 | } 122 | 123 | assert_eq!(count.load(Ordering::Relaxed), EXPECTED); 124 | } 125 | -------------------------------------------------------------------------------- /tests/src/hll.rs: -------------------------------------------------------------------------------- 1 | use crate::common; 2 | use env_logger; 3 | 4 | use aerospike::operations::hll; 5 | use aerospike::operations::hll::HLLPolicy; 6 | use aerospike::{as_key, as_list, as_val, Bins, FloatValue, ReadPolicy, Value, WritePolicy}; 7 | 8 | #[test] 9 | fn hll() { 10 | let _ = env_logger::try_init(); 11 | 12 | let client = common::client(); 13 | let namespace = common::namespace(); 14 | let set_name = &common::rand_str(10); 15 | 16 | let key = as_key!(namespace, set_name, "test"); 17 | 18 | let hpolicy = HLLPolicy::default(); 19 | let wpolicy = WritePolicy::default(); 20 | let rpolicy = ReadPolicy::default(); 21 | 22 | let ops = &vec![hll::init(&hpolicy, "bin", 4)]; 23 | client.operate(&wpolicy, &key, ops).unwrap(); 24 | 25 | let v = vec![Value::from("asd123")]; 26 | let ops = &vec![hll::add(&hpolicy, "bin", &v)]; 27 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 28 | assert_eq!( 29 | *rec.bins.get("bin").unwrap(), 30 | Value::Int(1), 31 | "Register update did not match" 32 | ); 33 | 34 | let ops = &vec![hll::get_count("bin")]; 35 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 36 | assert_eq!( 37 | *rec.bins.get("bin").unwrap(), 38 | Value::Int(1), 39 | "HLL Count did not match" 40 | ); 41 | 42 | let ops = &vec![hll::init_with_min_hash(&hpolicy, "bin2", 8, 0)]; 43 | client.operate(&wpolicy, &key, ops).unwrap(); 44 | 45 | let ops = &vec![hll::fold("bin2", 6)]; 46 | client.operate(&wpolicy, &key, ops).unwrap(); 47 | 48 | let v2 = vec![Value::from("123asd")]; 49 | let ops = &vec![hll::add(&hpolicy, "bin2", &v2)]; 50 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 51 | assert_eq!( 52 | *rec.bins.get("bin2").unwrap(), 53 | Value::Int(1), 54 | "Register update did not match" 55 | ); 56 | 57 | let ops = &vec![hll::describe("bin")]; 58 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 59 | assert_eq!( 60 | *rec.bins.get("bin").unwrap(), 61 | as_list!(4, 0), 62 | "Index bits did not match" 63 | ); 64 | 65 | let rec = client.get(&rpolicy, &key, Bins::from(["bin2"])).unwrap(); 66 | let bin2val = vec![rec.bins.get("bin2").unwrap().clone()]; 67 | 68 | let ops = &vec![hll::get_intersect_count("bin", &bin2val)]; 69 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 70 | assert_eq!( 71 | *rec.bins.get("bin").unwrap(), 72 | Value::from(0), 73 | "Intersect Count is wrong" 74 | ); 75 | 76 | let ops = &vec![hll::get_union_count("bin", &bin2val)]; 77 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 78 | assert_eq!( 79 | *rec.bins.get("bin").unwrap(), 80 | Value::from(2), 81 | "Union Count is wrong" 82 | ); 83 | 84 | let ops = &vec![hll::get_union("bin", &bin2val)]; 85 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 86 | let val = Value::HLL(vec![ 87 | 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88 | ]); 89 | assert_eq!(*rec.bins.get("bin").unwrap(), val, "Union does not match"); 90 | 91 | let ops = &vec![hll::refresh_count("bin")]; 92 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 93 | assert_eq!( 94 | *rec.bins.get("bin").unwrap(), 95 | Value::Int(1), 96 | "HLL Refresh Count did not match" 97 | ); 98 | 99 | let ops = &vec![ 100 | hll::set_union(&hpolicy, "bin", &bin2val), 101 | hll::get_count("bin"), 102 | ]; 103 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 104 | assert_eq!( 105 | *rec.bins.get("bin").unwrap(), 106 | Value::from(2), 107 | "Written Union count does not match" 108 | ); 109 | 110 | let ops = &vec![hll::get_similarity("bin", &bin2val)]; 111 | let rec = client.operate(&wpolicy, &key, ops).unwrap(); 112 | assert_eq!( 113 | *rec.bins.get("bin").unwrap(), 114 | Value::Float(FloatValue::F64(4602678819172646912)), 115 | "Similarity failed" 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/net/connection.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::io::prelude::*; 17 | use std::net::{Shutdown, TcpStream, ToSocketAddrs}; 18 | use std::ops::Add; 19 | use std::time::{Duration, Instant}; 20 | 21 | use crate::commands::admin_command::AdminCommand; 22 | use crate::commands::buffer::Buffer; 23 | use crate::errors::Result; 24 | use crate::policy::ClientPolicy; 25 | 26 | #[derive(Debug)] 27 | pub struct Connection { 28 | // duration after which connection is considered idle 29 | idle_timeout: Option, 30 | idle_deadline: Option, 31 | 32 | // connection object 33 | conn: TcpStream, 34 | 35 | bytes_read: usize, 36 | 37 | pub buffer: Buffer, 38 | } 39 | 40 | impl Connection { 41 | pub fn new(addr: T, policy: &ClientPolicy) -> Result { 42 | let stream = TcpStream::connect(addr)?; 43 | let mut conn = Connection { 44 | buffer: Buffer::new(policy.buffer_reclaim_threshold), 45 | bytes_read: 0, 46 | conn: stream, 47 | idle_timeout: policy.idle_timeout, 48 | idle_deadline: match policy.idle_timeout { 49 | None => None, 50 | Some(timeout) => Some(Instant::now() + timeout), 51 | }, 52 | }; 53 | conn.authenticate(&policy.user_password)?; 54 | conn.refresh(); 55 | Ok(conn) 56 | } 57 | 58 | pub fn close(&mut self) { 59 | let _ = self.conn.shutdown(Shutdown::Both); 60 | } 61 | 62 | pub fn flush(&mut self) -> Result<()> { 63 | self.conn.write_all(&self.buffer.data_buffer)?; 64 | self.refresh(); 65 | Ok(()) 66 | } 67 | 68 | pub fn read_buffer(&mut self, size: usize) -> Result<()> { 69 | self.buffer.resize_buffer(size)?; 70 | self.conn.read_exact(&mut self.buffer.data_buffer)?; 71 | self.bytes_read += size; 72 | self.buffer.reset_offset()?; 73 | self.refresh(); 74 | Ok(()) 75 | } 76 | 77 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { 78 | self.conn.write_all(buf)?; 79 | self.refresh(); 80 | Ok(()) 81 | } 82 | 83 | pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { 84 | self.conn.read_exact(buf)?; 85 | self.bytes_read += buf.len(); 86 | self.refresh(); 87 | Ok(()) 88 | } 89 | 90 | pub fn set_timeout(&self, timeout: Option) -> Result<()> { 91 | self.conn.set_read_timeout(timeout)?; 92 | self.conn.set_write_timeout(timeout)?; 93 | Ok(()) 94 | } 95 | 96 | pub fn is_idle(&self) -> bool { 97 | self.idle_deadline 98 | .map_or(false, |idle_dl| Instant::now() >= idle_dl) 99 | } 100 | 101 | fn refresh(&mut self) { 102 | self.idle_deadline = None; 103 | if let Some(idle_to) = self.idle_timeout { 104 | self.idle_deadline = Some(Instant::now().add(idle_to)) 105 | }; 106 | } 107 | 108 | fn authenticate(&mut self, user_password: &Option<(String, String)>) -> Result<()> { 109 | if let Some((ref user, ref password)) = *user_password { 110 | match AdminCommand::authenticate(self, user, password) { 111 | Ok(()) => { 112 | return Ok(()); 113 | } 114 | Err(err) => { 115 | self.close(); 116 | return Err(err); 117 | } 118 | } 119 | } 120 | 121 | Ok(()) 122 | } 123 | 124 | pub fn bookmark(&mut self) { 125 | self.bytes_read = 0; 126 | } 127 | 128 | pub const fn bytes_read(&self) -> usize { 129 | self.bytes_read 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/task/index_task.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::cluster::Cluster; 17 | use crate::errors::{ErrorKind, Result}; 18 | use crate::task::{Status, Task}; 19 | use std::sync::Arc; 20 | 21 | /// Struct for querying index creation status 22 | #[derive(Debug, Clone)] 23 | pub struct IndexTask { 24 | cluster: Arc, 25 | namespace: String, 26 | index_name: String, 27 | } 28 | 29 | static SUCCESS_PATTERN: &str = "load_pct="; 30 | static FAIL_PATTERN_201: &str = "FAIL:201"; 31 | static FAIL_PATTERN_203: &str = "FAIL:203"; 32 | static DELMITER: &str = ";"; 33 | 34 | impl IndexTask { 35 | /// Initializes `IndexTask` from client, creation should only be expose to Client 36 | pub fn new(cluster: Arc, namespace: String, index_name: String) -> Self { 37 | IndexTask { 38 | cluster, 39 | namespace, 40 | index_name, 41 | } 42 | } 43 | 44 | fn build_command(namespace: String, index_name: String) -> String { 45 | return format!("sindex/{}/{}", namespace, index_name); 46 | } 47 | 48 | fn parse_response(response: &str) -> Result { 49 | match response.find(SUCCESS_PATTERN) { 50 | None => { 51 | if response.contains(FAIL_PATTERN_201) || response.contains(FAIL_PATTERN_203) { 52 | Ok(Status::NotFound) 53 | } else { 54 | bail!(ErrorKind::BadResponse(format!( 55 | "Code 201 and 203 missing. Response: {}", 56 | response 57 | ))); 58 | } 59 | } 60 | Some(pattern_index) => { 61 | let percent_begin = pattern_index + SUCCESS_PATTERN.len(); 62 | 63 | let percent_end = match response[percent_begin..].find(DELMITER) { 64 | None => bail!(ErrorKind::BadResponse(format!( 65 | "delimiter missing in response. Response: {}", 66 | response 67 | ))), 68 | Some(percent_end) => percent_end, 69 | }; 70 | let percent_str = &response[percent_begin..percent_begin + percent_end]; 71 | match percent_str.parse::() { 72 | Ok(100) => Ok(Status::Complete), 73 | Ok(_) => Ok(Status::InProgress), 74 | Err(_) => bail!(ErrorKind::BadResponse( 75 | "Unexpected load_pct value from server".to_string() 76 | )), 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | impl Task for IndexTask { 84 | /// Query the status of index creation across all nodes 85 | fn query_status(&self) -> Result { 86 | let nodes = self.cluster.nodes(); 87 | 88 | if nodes.is_empty() { 89 | bail!(ErrorKind::Connection("No connected node".to_string())) 90 | } 91 | 92 | for node in &nodes { 93 | let command = 94 | &IndexTask::build_command(self.namespace.to_owned(), self.index_name.to_owned()); 95 | let response = node.info( 96 | Some(self.cluster.client_policy().timeout.unwrap()), 97 | &[&command[..]], 98 | )?; 99 | 100 | if !response.contains_key(command) { 101 | return Ok(Status::NotFound); 102 | } 103 | 104 | match IndexTask::parse_response(&response[command]) { 105 | Ok(Status::Complete) => {} 106 | in_progress_or_error => return in_progress_or_error, 107 | } 108 | } 109 | Ok(Status::Complete) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::value::Value; 17 | #[cfg(feature = "serialization")] 18 | use serde::Serialize; 19 | use std::convert::From; 20 | 21 | /// Container object for a record bin, comprising a name and a value. 22 | pub struct Bin<'a> { 23 | /// Bin name 24 | pub name: &'a str, 25 | 26 | /// Bin value 27 | pub value: Value, 28 | } 29 | 30 | impl<'a> Bin<'a> { 31 | /// Construct a new bin given a name and a value. 32 | pub const fn new(name: &'a str, val: Value) -> Self { 33 | Bin { name, value: val } 34 | } 35 | } 36 | 37 | impl<'a> AsRef> for Bin<'a> { 38 | fn as_ref(&self) -> &Self { 39 | self 40 | } 41 | } 42 | 43 | /// Construct a new bin from a name and an optional value (defaults to the empty value `nil`). 44 | #[macro_export] 45 | macro_rules! as_bin { 46 | ($bin_name:expr, None) => {{ 47 | $crate::Bin::new($bin_name, $crate::Value::Nil) 48 | }}; 49 | ($bin_name:expr, $val:expr) => {{ 50 | $crate::Bin::new($bin_name, $crate::Value::from($val)) 51 | }}; 52 | } 53 | 54 | /// Specify which, if any, bins to return in read operations. 55 | #[derive(Debug, Clone, PartialEq, Eq)] 56 | #[cfg_attr(feature = "serialization", derive(Serialize))] 57 | pub enum Bins { 58 | /// Read all bins. 59 | All, 60 | 61 | /// Read record header (generation, expiration) only. 62 | None, 63 | 64 | /// Read specified bin names only. 65 | Some(Vec), 66 | } 67 | 68 | impl Bins { 69 | /// Returns `true` if the bins selector is an `All` value. 70 | pub const fn is_all(&self) -> bool { 71 | matches!(*self, Bins::All) 72 | } 73 | 74 | /// Returns `true` if the bins selector is a `None` value. 75 | pub const fn is_none(&self) -> bool { 76 | matches!(*self, Bins::None) 77 | } 78 | } 79 | 80 | impl<'a> From<&'a [&'a str]> for Bins { 81 | fn from(bins: &'a [&'a str]) -> Self { 82 | let bins = bins.iter().cloned().map(String::from).collect(); 83 | Bins::Some(bins) 84 | } 85 | } 86 | 87 | impl<'a> From<[&'a str; 1]> for Bins { 88 | fn from(bins: [&'a str; 1]) -> Self { 89 | let bins = bins.iter().cloned().map(String::from).collect(); 90 | Bins::Some(bins) 91 | } 92 | } 93 | 94 | impl<'a> From<[&'a str; 2]> for Bins { 95 | fn from(bins: [&'a str; 2]) -> Self { 96 | let bins = bins.iter().cloned().map(String::from).collect(); 97 | Bins::Some(bins) 98 | } 99 | } 100 | 101 | impl<'a> From<[&'a str; 3]> for Bins { 102 | fn from(bins: [&'a str; 3]) -> Self { 103 | let bins = bins.iter().cloned().map(String::from).collect(); 104 | Bins::Some(bins) 105 | } 106 | } 107 | 108 | impl<'a> From<[&'a str; 4]> for Bins { 109 | fn from(bins: [&'a str; 4]) -> Self { 110 | let bins = bins.iter().cloned().map(String::from).collect(); 111 | Bins::Some(bins) 112 | } 113 | } 114 | 115 | impl<'a> From<[&'a str; 5]> for Bins { 116 | fn from(bins: [&'a str; 5]) -> Self { 117 | let bins = bins.iter().cloned().map(String::from).collect(); 118 | Bins::Some(bins) 119 | } 120 | } 121 | 122 | impl<'a> From<[&'a str; 6]> for Bins { 123 | fn from(bins: [&'a str; 6]) -> Self { 124 | let bins = bins.iter().cloned().map(String::from).collect(); 125 | Bins::Some(bins) 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::{Bins, From}; 132 | 133 | #[test] 134 | fn into_bins() { 135 | let bin_names = vec!["a".to_string(), "b".to_string(), "c".to_string()]; 136 | let expected = Bins::Some(bin_names); 137 | 138 | assert_eq!(expected, Bins::from(["a", "b", "c"])); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/operations/cdt_context.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Operation Context for nested Operations 16 | use crate::operations::lists::{list_order_flag, ListOrderType}; 17 | use crate::operations::MapOrder; 18 | use crate::Value; 19 | use crate::operations::maps::map_order_flag; 20 | 21 | #[doc(hidden)] 22 | // Empty Context for scalar operations 23 | pub const DEFAULT_CTX: &[CdtContext] = &[]; 24 | 25 | #[doc(hidden)] 26 | pub enum CtxType { 27 | ListIndex = 0x10, 28 | ListRank = 0x11, 29 | ListValue = 0x13, 30 | MapIndex = 0x20, 31 | MapRank = 0x21, 32 | MapKey = 0x22, 33 | MapValue = 0x23, 34 | } 35 | /// `CdtContext` defines Nested CDT context. Identifies the location of nested list/map to apply the operation. 36 | /// for the current level. 37 | /// An array of CTX identifies location of the list/map on multiple 38 | /// levels on nesting. 39 | #[derive(Debug, Clone)] 40 | pub struct CdtContext { 41 | /// Context Type 42 | pub id: u8, 43 | 44 | /// Flags 45 | pub flags: u8, 46 | 47 | /// Context Value 48 | pub value: Value, 49 | } 50 | 51 | /// Defines Lookup list by index offset. 52 | /// If the index is negative, the resolved index starts backwards from end of list. 53 | /// If an index is out of bounds, a parameter error will be returned. 54 | /// Examples: 55 | /// 0: First item. 56 | /// 4: Fifth item. 57 | /// -1: Last item. 58 | /// -3: Third to last item. 59 | pub const fn ctx_list_index(index: i64) -> CdtContext { 60 | CdtContext { 61 | id: CtxType::ListIndex as u8, 62 | flags: 0, 63 | value: Value::Int(index), 64 | } 65 | } 66 | 67 | /// list with given type at index offset, given an order and pad. 68 | pub const fn ctx_list_index_create(index: i64, order: ListOrderType, pad: bool) -> CdtContext { 69 | CdtContext { 70 | id: CtxType::ListIndex as u8, 71 | flags: list_order_flag(order, pad), 72 | value: Value::Int(index), 73 | } 74 | } 75 | 76 | /// Defines Lookup list by rank. 77 | /// 0 = smallest value 78 | /// N = Nth smallest value 79 | /// -1 = largest value 80 | pub const fn ctx_list_rank(rank: i64) -> CdtContext { 81 | CdtContext { 82 | id: CtxType::ListRank as u8, 83 | flags: 0, 84 | value: Value::Int(rank), 85 | } 86 | } 87 | 88 | /// Defines Lookup list by value. 89 | pub const fn ctx_list_value(key: Value) -> CdtContext { 90 | CdtContext { 91 | id: CtxType::ListValue as u8, 92 | flags: 0, 93 | value: key, 94 | } 95 | } 96 | /// Defines Lookup map by index offset. 97 | /// If the index is negative, the resolved index starts backwards from end of list. 98 | /// If an index is out of bounds, a parameter error will be returned. 99 | /// Examples: 100 | /// 0: First item. 101 | /// 4: Fifth item. 102 | /// -1: Last item. 103 | /// -3: Third to last item. 104 | pub const fn ctx_map_index(key: Value) -> CdtContext { 105 | CdtContext { 106 | id: CtxType::MapIndex as u8, 107 | flags: 0, 108 | value: key, 109 | } 110 | } 111 | 112 | /// Defines Lookup map by rank. 113 | /// 0 = smallest value 114 | /// N = Nth smallest value 115 | /// -1 = largest value 116 | pub const fn ctx_map_rank(rank: i64) -> CdtContext { 117 | CdtContext { 118 | id: CtxType::MapRank as u8, 119 | flags: 0, 120 | value: Value::Int(rank), 121 | } 122 | } 123 | 124 | /// Defines Lookup map by key. 125 | pub const fn ctx_map_key(key: Value) -> CdtContext { 126 | CdtContext { 127 | id: CtxType::MapKey as u8, 128 | flags: 0, 129 | value: key, 130 | } 131 | } 132 | 133 | /// Create map with given type at map key. 134 | pub const fn ctx_map_key_create(key: Value, order: MapOrder) -> CdtContext { 135 | CdtContext { 136 | id: CtxType::MapKey as u8, 137 | flags: map_order_flag(order), 138 | value: key, 139 | } 140 | } 141 | 142 | /// Defines Lookup map by value. 143 | pub const fn ctx_map_value(key: Value) -> CdtContext { 144 | CdtContext { 145 | id: CtxType::MapValue as u8, 146 | flags: 0, 147 | value: key, 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/record.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache Licenseersion 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | #[cfg(feature = "serialization")] 17 | use serde::Serialize; 18 | 19 | use std::collections::HashMap; 20 | use std::fmt; 21 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 22 | 23 | use crate::Key; 24 | use crate::Value; 25 | 26 | lazy_static! { 27 | // Fri Jan 1 00:00:00 UTC 2010 28 | pub static ref CITRUSLEAF_EPOCH: SystemTime = UNIX_EPOCH + Duration::new(1_262_304_000, 0); 29 | } 30 | 31 | /// Container object for a database record. 32 | #[derive(Debug)] 33 | #[cfg_attr(feature = "serialization", derive(Serialize))] 34 | pub struct Record { 35 | /// Record key. When reading a record from the database, the key is not set in the returned 36 | /// Record struct. 37 | pub key: Option, 38 | 39 | /// Map of named record bins. 40 | pub bins: HashMap, 41 | 42 | /// Record modification count. 43 | pub generation: u32, 44 | 45 | /// Date record will expire, in seconds from Jan 01 2010, 00:00:00 UTC. 46 | expiration: u32, 47 | } 48 | 49 | impl Record { 50 | /// Construct a new Record. For internal use only. 51 | #[doc(hidden)] 52 | pub const fn new( 53 | key: Option, 54 | bins: HashMap, 55 | generation: u32, 56 | expiration: u32, 57 | ) -> Self { 58 | Record { 59 | key, 60 | bins, 61 | generation, 62 | expiration, 63 | } 64 | } 65 | 66 | /// Returns the remaining time-to-live (TTL, a.k.a. expiration time) for the record or `None` 67 | /// if the record never expires. 68 | pub fn time_to_live(&self) -> Option { 69 | match self.expiration { 70 | 0 => None, 71 | secs_since_epoch => { 72 | let expiration = *CITRUSLEAF_EPOCH + Duration::new(u64::from(secs_since_epoch), 0); 73 | match expiration.duration_since(SystemTime::now()) { 74 | Ok(d) => Some(d), 75 | // Record was not expired at server but it looks expired at client 76 | // because of delay or clock difference, present it as not-expired. 77 | Err(_) => Some(Duration::new(1u64, 0)), 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | impl fmt::Display for Record { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 86 | write!(f, "key: {:?}", self.key)?; 87 | write!(f, ", bins: {{")?; 88 | for (i, (k, v)) in self.bins.iter().enumerate() { 89 | if i > 0 { 90 | write!(f, ", ")? 91 | } 92 | write!(f, "{}: {}", k, v)?; 93 | } 94 | write!(f, "}}, generation: {}", self.generation)?; 95 | write!(f, ", ttl: ")?; 96 | match self.time_to_live() { 97 | None => "none".fmt(f), 98 | Some(duration) => duration.as_secs().fmt(f), 99 | } 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::{Record, CITRUSLEAF_EPOCH}; 106 | use std::collections::HashMap; 107 | use std::time::{Duration, SystemTime}; 108 | 109 | #[test] 110 | fn ttl_expiration_future() { 111 | let expiration = SystemTime::now() + Duration::new(1000, 0); 112 | let secs_since_epoch = expiration 113 | .duration_since(*CITRUSLEAF_EPOCH) 114 | .unwrap() 115 | .as_secs(); 116 | let record = Record::new(None, HashMap::new(), 0, secs_since_epoch as u32); 117 | let ttl = record.time_to_live(); 118 | assert!(ttl.is_some()); 119 | assert!(1000 - ttl.unwrap().as_secs() <= 1); 120 | } 121 | 122 | #[test] 123 | fn ttl_expiration_past() { 124 | let record = Record::new(None, HashMap::new(), 0, 0x0d00_d21c); 125 | assert_eq!(record.time_to_live(), Some(Duration::new(1u64, 0))); 126 | } 127 | 128 | #[test] 129 | fn ttl_never_expires() { 130 | let record = Record::new(None, HashMap::new(), 0, 0); 131 | assert_eq!(record.time_to_live(), None); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/operations/exp.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | //! Expression Operations. 17 | //! This functions allow users to run `FilterExpressions` as Operate commands. 18 | 19 | use crate::commands::buffer::Buffer; 20 | use crate::errors::Result; 21 | use crate::expressions::FilterExpression; 22 | use crate::msgpack::encoder::{pack_array_begin, pack_integer}; 23 | use crate::operations::{Operation, OperationBin, OperationData, OperationType}; 24 | use crate::ParticleType; 25 | 26 | /// Expression write Flags 27 | pub enum ExpWriteFlags { 28 | /// Default. Allow create or update. 29 | Default = 0, 30 | /// If bin does not exist, a new bin will be created. 31 | /// If bin exists, the operation will be denied. 32 | /// If bin exists, fail with Bin Exists 33 | CreateOnly = 1 << 0, 34 | /// If bin exists, the bin will be overwritten. 35 | /// If bin does not exist, the operation will be denied. 36 | /// If bin does not exist, fail with Bin Not Found 37 | UpdateOnly = 1 << 1, 38 | /// If expression results in nil value, then delete the bin. 39 | /// Otherwise, return OP Not Applicable when NoFail is not set 40 | AllowDelete = 1 << 2, 41 | /// Do not raise error if operation is denied. 42 | PolicyNoFail = 1 << 3, 43 | /// Ignore failures caused by the expression resolving to unknown or a non-bin type. 44 | EvalNoFail = 1 << 4, 45 | } 46 | 47 | #[doc(hidden)] 48 | pub type ExpressionEncoder = Box, &ExpOperation) -> Result>; 49 | 50 | #[doc(hidden)] 51 | pub struct ExpOperation<'a> { 52 | pub encoder: ExpressionEncoder, 53 | pub policy: i64, 54 | pub exp: &'a FilterExpression, 55 | } 56 | 57 | impl<'a> ExpOperation<'a> { 58 | #[doc(hidden)] 59 | pub const fn particle_type(&self) -> ParticleType { 60 | ParticleType::BLOB 61 | } 62 | #[doc(hidden)] 63 | pub fn estimate_size(&self) -> Result { 64 | let size: usize = (self.encoder)(&mut None, self)?; 65 | Ok(size) 66 | } 67 | #[doc(hidden)] 68 | pub fn write_to(&self, buffer: &mut Buffer) -> Result { 69 | let size: usize = (self.encoder)(&mut Some(buffer), self)?; 70 | Ok(size) 71 | } 72 | } 73 | 74 | /// Expression read Flags 75 | pub enum ExpReadFlags { 76 | /// Default 77 | Default = 0, 78 | /// Ignore failures caused by the expression resolving to unknown or a non-bin type. 79 | EvalNoFail = 1 << 4, 80 | } 81 | 82 | /// Create operation that performs a expression that writes to record bin. 83 | pub fn write_exp<'a>( 84 | bin: &'a str, 85 | exp: &'a FilterExpression, 86 | flags: ExpWriteFlags, 87 | ) -> Operation<'a> { 88 | let op = ExpOperation { 89 | encoder: Box::new(pack_write_exp), 90 | policy: flags as i64, 91 | exp, 92 | }; 93 | Operation { 94 | op: OperationType::ExpWrite, 95 | ctx: &[], 96 | bin: OperationBin::Name(bin), 97 | data: OperationData::EXPOp(op), 98 | } 99 | } 100 | 101 | /// Create operation that performs a read expression. 102 | pub fn read_exp<'a>( 103 | name: &'a str, 104 | exp: &'a FilterExpression, 105 | flags: ExpReadFlags, 106 | ) -> Operation<'a> { 107 | let op = ExpOperation { 108 | encoder: Box::new(pack_read_exp), 109 | policy: flags as i64, 110 | exp, 111 | }; 112 | Operation { 113 | op: OperationType::ExpRead, 114 | ctx: &[], 115 | bin: OperationBin::Name(name), 116 | data: OperationData::EXPOp(op), 117 | } 118 | } 119 | 120 | fn pack_write_exp(buf: &mut Option<&mut Buffer>, exp_op: &ExpOperation) -> Result { 121 | let mut size = 0; 122 | size += pack_array_begin(buf, 2)?; 123 | size += exp_op.exp.pack(buf)?; 124 | size += pack_integer(buf, exp_op.policy)?; 125 | Ok(size) 126 | } 127 | 128 | fn pack_read_exp(buf: &mut Option<&mut Buffer>, exp_op: &ExpOperation) -> Result { 129 | let mut size = 0; 130 | size += pack_array_begin(buf, 2)?; 131 | size += exp_op.exp.pack(buf)?; 132 | size += pack_integer(buf, exp_op.policy)?; 133 | Ok(size) 134 | } 135 | -------------------------------------------------------------------------------- /src/policy/write_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::expressions::FilterExpression; 17 | use crate::policy::{BasePolicy, PolicyLike}; 18 | use crate::{CommitLevel, Expiration, GenerationPolicy, RecordExistsAction}; 19 | 20 | /// `WritePolicy` encapsulates parameters for all write operations. 21 | #[derive(Debug, Clone)] 22 | pub struct WritePolicy { 23 | /// Base policy instance 24 | pub base_policy: BasePolicy, 25 | 26 | /// RecordExistsAction qualifies how to handle writes where the record already exists. 27 | pub record_exists_action: RecordExistsAction, 28 | 29 | /// GenerationPolicy qualifies how to handle record writes based on record generation. 30 | /// The default (NONE) indicates that the generation is not used to restrict writes. 31 | pub generation_policy: GenerationPolicy, 32 | 33 | /// Desired consistency guarantee when committing a transaction on the server. The default 34 | /// (COMMIT_ALL) indicates that the server should wait for master and all replica commits to 35 | /// be successful before returning success to the client. 36 | pub commit_level: CommitLevel, 37 | 38 | /// Generation determines expected generation. 39 | /// Generation is the number of times a record has been 40 | /// modified (including creation) on the server. 41 | /// If a write operation is creating a record, the expected generation would be 0. 42 | pub generation: u32, 43 | 44 | /// Expiration determimes record expiration in seconds. Also known as TTL (Time-To-Live). 45 | /// Seconds record will live before being removed by the server. 46 | pub expiration: Expiration, 47 | 48 | /// Send user defined key in addition to hash digest on a record put. 49 | /// The default is to not send the user defined key. 50 | pub send_key: bool, 51 | 52 | /// For Client::operate() method, return a result for every operation. 53 | /// Some list operations do not return results by default (`operations::list::clear()` for 54 | /// example). This can sometimes make it difficult to determine the desired result offset in 55 | /// the returned bin's result list. 56 | /// 57 | /// Setting RespondPerEachOp to true makes it easier to identify the desired result offset 58 | /// (result offset equals bin's operate sequence). This only makes sense when multiple list 59 | /// operations are used in one operate call and some of those operations do not return results 60 | /// by default. 61 | pub respond_per_each_op: bool, 62 | 63 | /// If the transaction results in a record deletion, leave a tombstone for the record. This 64 | /// prevents deleted records from reappearing after node failures. Valid for Aerospike Server 65 | /// Enterprise Edition 3.10+ only. 66 | pub durable_delete: bool, 67 | 68 | /// Optional Filter Expression 69 | pub filter_expression: Option, 70 | } 71 | 72 | impl WritePolicy { 73 | /// Create a new write policy instance with the specified generation and expiration parameters. 74 | pub fn new(gen: u32, exp: Expiration) -> Self { 75 | Self { 76 | generation: gen, 77 | expiration: exp, 78 | ..WritePolicy::default() 79 | } 80 | } 81 | 82 | /// Get the current Filter expression 83 | pub const fn filter_expression(&self) -> &Option { 84 | &self.filter_expression 85 | } 86 | } 87 | 88 | impl Default for WritePolicy { 89 | fn default() -> Self { 90 | WritePolicy { 91 | base_policy: BasePolicy::default(), 92 | record_exists_action: RecordExistsAction::Update, 93 | generation_policy: GenerationPolicy::None, 94 | commit_level: CommitLevel::CommitAll, 95 | generation: 0, 96 | expiration: Expiration::NamespaceDefault, 97 | send_key: false, 98 | respond_per_each_op: false, 99 | durable_delete: false, 100 | filter_expression: None, 101 | } 102 | } 103 | } 104 | 105 | impl PolicyLike for WritePolicy { 106 | fn base(&self) -> &BasePolicy { 107 | &self.base_policy 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /.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 2017 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 | # Beta 64-bit MSVC 44 | - channel: beta 45 | target: x86_64-pc-windows-msvc 46 | # Nightly 64-bit MSVC 47 | - channel: nightly 48 | target: x86_64-pc-windows-msvc 49 | #cargoflags: --features "unstable" 50 | # # Stable 32-bit MSVC 51 | # - channel: stable 52 | # target: i686-pc-windows-msvc 53 | # # Beta 32-bit MSVC 54 | # - channel: beta 55 | # target: i686-pc-windows-msvc 56 | # # Nightly 32-bit MSVC 57 | # - channel: nightly 58 | # target: i686-pc-windows-msvc 59 | # #cargoflags: --features "unstable" 60 | 61 | # ### GNU Toolchains ### 62 | # 63 | # # Stable 64-bit GNU 64 | # - channel: stable 65 | # target: x86_64-pc-windows-gnu 66 | # # Stable 32-bit GNU 67 | # - channel: stable 68 | # target: i686-pc-windows-gnu 69 | # # Beta 64-bit GNU 70 | # - channel: beta 71 | # target: x86_64-pc-windows-gnu 72 | # # Beta 32-bit GNU 73 | # - channel: beta 74 | # target: i686-pc-windows-gnu 75 | # # Nightly 64-bit GNU 76 | # - channel: nightly 77 | # target: x86_64-pc-windows-gnu 78 | # #cargoflags: --features "unstable" 79 | # # Nightly 32-bit GNU 80 | # - channel: nightly 81 | # target: i686-pc-windows-gnu 82 | # #cargoflags: --features "unstable" 83 | 84 | ### Allowed failures ### 85 | 86 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 87 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 88 | # or test failure in the matching channels/targets from failing the entire build. 89 | matrix: 90 | allow_failures: 91 | - channel: nightly 92 | 93 | # If you only care about stable channel build failures, uncomment the following line: 94 | #- channel: beta 95 | 96 | ## Install Script ## 97 | 98 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 99 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 100 | # rustup to install Rust. 101 | # 102 | # For simple configurations, instead of using the build matrix, you can simply set the 103 | # default-toolchain and default-host manually here. 104 | install: 105 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 106 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 107 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 108 | - rustc -vV 109 | - cargo -vV 110 | - docker version 111 | 112 | ## Build Script ## 113 | 114 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 115 | # the "directory does not contain a project or solution file" error. 116 | build: false 117 | 118 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 119 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 120 | # environment variable. 121 | test_script: 122 | - cargo test --lib --verbose %cargoflags% 123 | - cargo test --lib --features "serialization" --verbose %cargoflags% 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aerospike Rust Client [![crates-io][crates-io-image]][crates-io-url] [![docs][docs-image]][docs-url] [![travis][travis-image]][travis-url] [![appveyor][appveyor-image]][appveyor-url] 2 | 3 | > [!NOTE] 4 | > Developement for the version 2 of the Rust client, which includes async support and modern Aerospike functionality happens in the [v2 branch](https://github.com/aerospike/aerospike-client-rust/tree/v2). Get involved! 5 | 6 | [crates-io-image]: https://img.shields.io/crates/v/aerospike.svg 7 | [crates-io-url]: https://crates.io/crates/aerospike 8 | [docs-image]: https://docs.rs/aerospike/badge.svg 9 | [docs-url]: https://docs.rs/aerospike/ 10 | [travis-image]: https://travis-ci.org/aerospike/aerospike-client-rust.svg?branch=master 11 | [travis-url]: https://travis-ci.org/aerospike/aerospike-client-rust 12 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/e9gx1b5d1307hj2t/branch/master?svg=true 13 | [appveyor-url]: https://ci.appveyor.com/project/aerospike/aerospike-client-rust/branch/master 14 | 15 | A community-supported [Aerospike](https://www.aerospike.com/) client library for Rust. Rust will become officially supported after the Rust client 2 alpha releases, currently in developer preview. 16 | 17 | This library is compatible with Rust 1.46+ and supports the following operating systems: Linux, Mac OS X, and Windows. 18 | The current release supports Aerospike version v5.6 and later. Take a look at the [changelog](CHANGELOG.md) for more details. 19 | 20 | - [Usage](#Usage) 21 | - [Known Limitations](#Limitations) 22 | - [Tests](#Tests) 23 | - [Benchmarks](#Benchmarks) 24 | 25 | 26 | ## Usage: 27 | 28 | The following is a very simple example of CRUD operations in an Aerospike database. 29 | 30 | ```rust 31 | #[macro_use] 32 | extern crate aerospike; 33 | 34 | use std::env; 35 | use std::time::Instant; 36 | 37 | use aerospike::{Bins, Client, ClientPolicy, ReadPolicy, WritePolicy}; 38 | use aerospike::operations; 39 | 40 | fn main() { 41 | let cpolicy = ClientPolicy::default(); 42 | let hosts = env::var("AEROSPIKE_HOSTS") 43 | .unwrap_or(String::from("127.0.0.1:3000")); 44 | let client = Client::new(&cpolicy, &hosts) 45 | .expect("Failed to connect to cluster"); 46 | 47 | let now = Instant::now(); 48 | let rpolicy = ReadPolicy::default(); 49 | let wpolicy = WritePolicy::default(); 50 | let key = as_key!("test", "test", "test"); 51 | 52 | let bins = [ 53 | as_bin!("int", 999), 54 | as_bin!("str", "Hello, World!"), 55 | ]; 56 | client.put(&wpolicy, &key, &bins).unwrap(); 57 | let rec = client.get(&rpolicy, &key, Bins::All); 58 | println!("Record: {}", rec.unwrap()); 59 | 60 | client.touch(&wpolicy, &key).unwrap(); 61 | let rec = client.get(&rpolicy, &key, Bins::All); 62 | println!("Record: {}", rec.unwrap()); 63 | 64 | let rec = client.get(&rpolicy, &key, Bins::None); 65 | println!("Record Header: {}", rec.unwrap()); 66 | 67 | let exists = client.exists(&wpolicy, &key).unwrap(); 68 | println!("exists: {}", exists); 69 | 70 | let bin = as_bin!("int", "123"); 71 | let ops = &vec![operations::put(&bin), operations::get()]; 72 | let op_rec = client.operate(&wpolicy, &key, ops); 73 | println!("operate: {}", op_rec.unwrap()); 74 | 75 | let existed = client.delete(&wpolicy, &key).unwrap(); 76 | println!("existed (should be true): {}", existed); 77 | 78 | let existed = client.delete(&wpolicy, &key).unwrap(); 79 | println!("existed (should be false): {}", existed); 80 | 81 | println!("total time: {:?}", now.elapsed()); 82 | } 83 | ``` 84 | 85 | 86 | ## Known Limitations 87 | 88 | The following features are not yet supported in the Aerospike Rust client: 89 | 90 | - Query Aggregation using Lua User-Defined Functions (UDF). 91 | - Secure connections using TLS. 92 | - IPv6 support. 93 | 94 | 95 | ## Tests 96 | 97 | This library is packaged with a number of tests. The tests assume that an 98 | Aerospike cluster is running at `localhost:3000`. To test using a cluster at a 99 | different address, set the `AEROSPIKE_HOSTS` environment variable to the list 100 | of cluster hosts. 101 | 102 | To run all the test cases: 103 | 104 | ```shell 105 | $ export AEROSPIKE_HOSTS=127.0.0.1:3000 106 | $ cargo test 107 | ``` 108 | 109 | To enable debug logging for the `aerospike` crate: 110 | 111 | ```shell 112 | $ RUST_LOG=aerospike=debug cargo test 113 | ``` 114 | 115 | To enable backtraces set the `RUST_BACKTRACE` environment variable: 116 | 117 | ```shell 118 | $ RUST_BACKTRACE=1 cargo test 119 | ``` 120 | 121 | 122 | ## Benchmarks 123 | 124 | The micro-benchmarks in the `benches` directory use the 125 | [`bencher`](https://crates.io/crates/bencher) crate and can be run on Rust 126 | stable releases: 127 | 128 | ```shell 129 | $ export AEROSPIKE_HOSTS=127.0.0.1:3000 130 | $ cargo bench 131 | ``` 132 | 133 | There is a separate benchmark tool under the 134 | [tools/benchmark](tools/benchmark) directory that is designed to 135 | insert data into an Aerospike server cluster and generate load. 136 | -------------------------------------------------------------------------------- /src/cluster/node_validator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::net::ToSocketAddrs; 16 | use std::str; 17 | use std::vec::Vec; 18 | 19 | use crate::cluster::Cluster; 20 | use crate::commands::Message; 21 | use crate::errors::{ErrorKind, Result, ResultExt}; 22 | use crate::net::{Connection, Host}; 23 | use crate::policy::ClientPolicy; 24 | 25 | // Validates a Database server node 26 | #[allow(clippy::struct_excessive_bools)] 27 | #[derive(Clone)] 28 | pub struct NodeValidator { 29 | pub name: String, 30 | pub aliases: Vec, 31 | pub address: String, 32 | pub client_policy: ClientPolicy, 33 | pub supports_float: bool, 34 | pub supports_batch_index: bool, 35 | pub supports_replicas_all: bool, 36 | pub supports_geo: bool, 37 | } 38 | 39 | // Generates a node validator 40 | impl NodeValidator { 41 | pub fn new(cluster: &Cluster) -> Self { 42 | NodeValidator { 43 | name: "".to_string(), 44 | aliases: vec![], 45 | address: "".to_string(), 46 | client_policy: cluster.client_policy().clone(), 47 | supports_float: false, 48 | supports_batch_index: false, 49 | supports_replicas_all: false, 50 | supports_geo: false, 51 | } 52 | } 53 | 54 | pub fn validate_node(&mut self, cluster: &Cluster, host: &Host) -> Result<()> { 55 | self.resolve_aliases(host) 56 | .chain_err(|| "Failed to resolve host aliases")?; 57 | 58 | let mut last_err = None; 59 | for alias in &self.aliases() { 60 | match self.validate_alias(cluster, alias) { 61 | Ok(_) => return Ok(()), 62 | Err(err) => { 63 | debug!("Alias {} failed: {:?}", alias, err); 64 | last_err = Some(err); 65 | } 66 | } 67 | } 68 | match last_err { 69 | Some(err) => Err(err), 70 | None => unreachable!(), 71 | } 72 | } 73 | 74 | pub fn aliases(&self) -> Vec { 75 | self.aliases.to_vec() 76 | } 77 | 78 | fn resolve_aliases(&mut self, host: &Host) -> Result<()> { 79 | self.aliases = (host.name.as_ref(), host.port) 80 | .to_socket_addrs()? 81 | .map(|addr| Host::new(&addr.ip().to_string(), addr.port())) 82 | .collect(); 83 | debug!("Resolved aliases for host {}: {:?}", host, self.aliases); 84 | if self.aliases.is_empty() { 85 | Err(ErrorKind::Connection(format!("Failed to find addresses for {}", host)).into()) 86 | } else { 87 | Ok(()) 88 | } 89 | } 90 | 91 | fn validate_alias(&mut self, cluster: &Cluster, alias: &Host) -> Result<()> { 92 | let mut conn = Connection::new(&alias, &self.client_policy)?; 93 | conn.set_timeout(self.client_policy.timeout)?; 94 | let info_map = Message::info(&mut conn, &["node", "cluster-name", "features"])?; 95 | 96 | match info_map.get("node") { 97 | None => bail!(ErrorKind::InvalidNode(String::from("Missing node name"))), 98 | Some(node_name) => self.name = node_name.to_owned(), 99 | } 100 | 101 | if let Some(ref cluster_name) = *cluster.cluster_name() { 102 | match info_map.get("cluster-name") { 103 | None => bail!(ErrorKind::InvalidNode(String::from("Missing cluster name"))), 104 | Some(info_name) if info_name == cluster_name => {} 105 | Some(info_name) => bail!(ErrorKind::InvalidNode(format!( 106 | "Cluster name mismatch: expected={}, 107 | got={}", 108 | cluster_name, info_name 109 | ))), 110 | } 111 | } 112 | 113 | self.address = alias.address(); 114 | 115 | if let Some(features) = info_map.get("features") { 116 | self.set_features(features); 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | fn set_features(&mut self, features: &str) { 123 | let features = features.split(';'); 124 | for feature in features { 125 | match feature { 126 | "float" => self.supports_float = true, 127 | "batch-index" => self.supports_batch_index = true, 128 | "replicas-all" => self.supports_replicas_all = true, 129 | "geo" => self.supports_geo = true, 130 | _ => (), 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/commands/single_command.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::sync::Arc; 16 | use std::thread; 17 | use std::time::Instant; 18 | 19 | use crate::cluster::partition::Partition; 20 | use crate::cluster::{Cluster, Node}; 21 | use crate::commands::{self}; 22 | use crate::errors::{ErrorKind, Result, ResultExt}; 23 | use crate::net::Connection; 24 | use crate::policy::Policy; 25 | use crate::Key; 26 | 27 | pub struct SingleCommand<'a> { 28 | cluster: Arc, 29 | pub key: &'a Key, 30 | partition: Partition<'a>, 31 | } 32 | 33 | impl<'a> SingleCommand<'a> { 34 | pub fn new(cluster: Arc, key: &'a Key) -> Self { 35 | let partition = Partition::new_by_key(key); 36 | SingleCommand { 37 | cluster, 38 | key, 39 | partition, 40 | } 41 | } 42 | 43 | pub fn get_node(&self) -> Result> { 44 | self.cluster.get_node(&self.partition) 45 | } 46 | 47 | pub fn empty_socket(conn: &mut Connection) -> Result<()> { 48 | // There should not be any more bytes. 49 | // Empty the socket to be safe. 50 | let sz = conn.buffer.read_i64(None)?; 51 | let header_length = i64::from(conn.buffer.read_u8(None)?); 52 | let receive_size = ((sz & 0xFFFF_FFFF_FFFF) - header_length) as usize; 53 | 54 | // Read remaining message bytes. 55 | if receive_size > 0 { 56 | conn.buffer.resize_buffer(receive_size)?; 57 | conn.read_buffer(receive_size)?; 58 | } 59 | 60 | Ok(()) 61 | } 62 | 63 | // EXECUTE 64 | // 65 | 66 | pub fn execute(policy: &dyn Policy, cmd: &'a mut dyn commands::Command) -> Result<()> { 67 | let mut iterations = 0; 68 | 69 | // set timeout outside the loop 70 | let deadline = policy.deadline(); 71 | 72 | // Execute command until successful, timed out or maximum iterations have been reached. 73 | loop { 74 | iterations += 1; 75 | 76 | // too many retries 77 | if let Some(max_retries) = policy.max_retries() { 78 | if iterations > max_retries + 1 { 79 | bail!(ErrorKind::Connection(format!( 80 | "Timeout after {} tries", 81 | iterations 82 | ))); 83 | } 84 | } 85 | 86 | // Sleep before trying again, after the first iteration 87 | if iterations > 1 { 88 | if let Some(sleep_between_retries) = policy.sleep_between_retries() { 89 | thread::sleep(sleep_between_retries); 90 | } 91 | } 92 | 93 | // check for command timeout 94 | if let Some(deadline) = deadline { 95 | if Instant::now() > deadline { 96 | break; 97 | } 98 | } 99 | 100 | // set command node, so when you return a record it has the node 101 | let node = match cmd.get_node() { 102 | Ok(node) => node, 103 | Err(_) => continue, // Node is currently inactive. Retry. 104 | }; 105 | 106 | let mut conn = match node.get_connection(policy.timeout()) { 107 | Ok(conn) => conn, 108 | Err(err) => { 109 | warn!("Node {}: {}", node, err); 110 | continue; 111 | } 112 | }; 113 | 114 | cmd.prepare_buffer(&mut conn) 115 | .chain_err(|| "Failed to prepare send buffer")?; 116 | cmd.write_timeout(&mut conn, policy.timeout()) 117 | .chain_err(|| "Failed to set timeout for send buffer")?; 118 | 119 | // Send command. 120 | if let Err(err) = cmd.write_buffer(&mut conn) { 121 | // IO errors are considered temporary anomalies. Retry. 122 | // Close socket to flush out possible garbage. Do not put back in pool. 123 | conn.invalidate(); 124 | warn!("Node {}: {}", node, err); 125 | continue; 126 | } 127 | 128 | // Parse results. 129 | if let Err(err) = cmd.parse_result(&mut conn) { 130 | // close the connection 131 | // cancelling/closing the batch/multi commands will return an error, which will 132 | // close the connection to throw away its data and signal the server about the 133 | // situation. We will not put back the connection in the buffer. 134 | if !commands::keep_connection(&err) { 135 | conn.invalidate(); 136 | } 137 | return Err(err); 138 | } 139 | 140 | // command has completed successfully. Exit method. 141 | return Ok(()); 142 | } 143 | 144 | bail!(ErrorKind::Connection("Timeout".to_string())) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/query/statement.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use crate::errors::{ErrorKind, Result}; 17 | use crate::query::Filter; 18 | use crate::Bins; 19 | use crate::Value; 20 | 21 | #[derive(Clone)] 22 | pub struct Aggregation { 23 | pub package_name: String, 24 | pub function_name: String, 25 | pub function_args: Option>, 26 | } 27 | 28 | /// Query statement parameters. 29 | pub struct Statement { 30 | /// Namespace 31 | pub namespace: String, 32 | 33 | /// Set name 34 | pub set_name: String, 35 | 36 | /// Optional index name 37 | pub index_name: Option, 38 | 39 | /// Optional list of bin names to return in query. 40 | pub bins: Bins, 41 | 42 | /// Optional list of query filters. Currently, only one filter is allowed by the server on a 43 | /// secondary index lookup. 44 | pub filters: Option>, 45 | 46 | /// Optional Lua aggregation function parameters. 47 | pub aggregation: Option, 48 | } 49 | 50 | impl Statement { 51 | /// Create a new query statement with the given namespace, set name and optional list of bin 52 | /// names. 53 | /// 54 | /// # Examples 55 | /// 56 | /// Create a new statement to query the namespace "foo" and set "bar" and return the "name" and 57 | /// "age" bins for each matching record. 58 | /// 59 | /// ```rust 60 | /// # use aerospike::*; 61 | /// 62 | /// let stmt = Statement::new("foo", "bar", Bins::from(["name", "age"])); 63 | /// ``` 64 | pub fn new(namespace: &str, set_name: &str, bins: Bins) -> Self { 65 | Statement { 66 | namespace: namespace.to_owned(), 67 | set_name: set_name.to_owned(), 68 | bins, 69 | index_name: None, 70 | aggregation: None, 71 | filters: None, 72 | } 73 | } 74 | 75 | /// Add a query filter to the statement. Currently, only one filter is allowed by the server on 76 | /// a secondary index lookup. 77 | /// 78 | /// # Example 79 | /// 80 | /// This example uses a numeric index on bin _baz_ in namespace _foo_ within set _bar_ to find 81 | /// all records using a filter with the range 0 to 100 inclusive: 82 | /// 83 | /// ```rust 84 | /// # use aerospike::*; 85 | /// 86 | /// let mut stmt = Statement::new("foo", "bar", Bins::from(["name", "age"])); 87 | /// stmt.add_filter(as_range!("baz", 0, 100)); 88 | /// ``` 89 | pub fn add_filter(&mut self, filter: Filter) { 90 | if let Some(ref mut filters) = self.filters { 91 | filters.push(filter); 92 | } else { 93 | let mut filters = vec![]; 94 | filters.push(filter); 95 | self.filters = Some(filters); 96 | } 97 | } 98 | 99 | /// Set Lua aggregation function parameters. 100 | pub fn set_aggregate_function( 101 | &mut self, 102 | package_name: &str, 103 | function_name: &str, 104 | function_args: Option<&[Value]>, 105 | ) { 106 | let agg = Aggregation { 107 | package_name: package_name.to_owned(), 108 | function_name: function_name.to_owned(), 109 | function_args: match function_args { 110 | Some(args) => Some(args.to_vec()), 111 | None => None, 112 | }, 113 | }; 114 | self.aggregation = Some(agg); 115 | } 116 | 117 | #[doc(hidden)] 118 | pub fn is_scan(&self) -> bool { 119 | match self.filters { 120 | Some(ref filters) => filters.is_empty(), 121 | None => true, 122 | } 123 | } 124 | 125 | #[doc(hidden)] 126 | pub fn validate(&self) -> Result<()> { 127 | if let Some(ref filters) = self.filters { 128 | if filters.len() > 1 { 129 | bail!(ErrorKind::InvalidArgument( 130 | "Too many filter expressions".to_string() 131 | )); 132 | } 133 | } 134 | 135 | if self.set_name.is_empty() { 136 | bail!(ErrorKind::InvalidArgument("Empty set name".to_string())); 137 | } 138 | 139 | if let Some(ref index_name) = self.index_name { 140 | if index_name.is_empty() { 141 | bail!(ErrorKind::InvalidArgument("Empty index name".to_string())); 142 | } 143 | } 144 | 145 | if let Some(ref agg) = self.aggregation { 146 | if agg.package_name.is_empty() { 147 | bail!(ErrorKind::InvalidArgument( 148 | "Empty UDF package name".to_string() 149 | )); 150 | } 151 | 152 | if agg.function_name.is_empty() { 153 | bail!(ErrorKind::InvalidArgument( 154 | "Empty UDF function name".to_string() 155 | )); 156 | } 157 | } 158 | 159 | Ok(()) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/policy/client_policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::collections::HashMap; 16 | use std::time::Duration; 17 | 18 | use crate::commands::admin_command::AdminCommand; 19 | use crate::errors::Result; 20 | 21 | /// `ClientPolicy` encapsulates parameters for client policy command. 22 | #[derive(Debug, Clone)] 23 | pub struct ClientPolicy { 24 | /// User authentication to cluster. Leave empty for clusters running without restricted access. 25 | pub user_password: Option<(String, String)>, 26 | 27 | /// Initial host connection timeout in milliseconds. The timeout when opening a connection 28 | /// to the server host for the first time. 29 | pub timeout: Option, 30 | 31 | /// Connection idle timeout. Every time a connection is used, its idle 32 | /// deadline will be extended by this duration. When this deadline is reached, 33 | /// the connection will be closed and discarded from the connection pool. 34 | pub idle_timeout: Option, 35 | 36 | /// Maximum number of synchronous connections allowed per server node. 37 | pub max_conns_per_node: usize, 38 | 39 | /// Number of connection pools used for each node. Machines with 8 CPU cores or less usually 40 | /// need only one connection pool per node. Machines with larger number of CPU cores may have 41 | /// their performance limited by contention for pooled connections. Contention for pooled 42 | /// connections can be reduced by creating multiple mini connection pools per node. 43 | pub conn_pools_per_node: usize, 44 | 45 | /// Throw exception if host connection fails during addHost(). 46 | pub fail_if_not_connected: bool, 47 | 48 | /// Threshold at which the buffer attached to the connection will be shrunk by deallocating 49 | /// memory instead of just resetting the size of the underlying vec. 50 | /// Should be set to a value that covers as large a percentile of payload sizes as possible, 51 | /// while also being small enough not to occupy a significant amount of memory for the life 52 | /// of the connection pool. 53 | pub buffer_reclaim_threshold: usize, 54 | 55 | /// TendInterval determines interval for checking for cluster state changes. 56 | /// Minimum possible interval is 10 Milliseconds. 57 | pub tend_interval: Duration, 58 | 59 | /// A IP translation table is used in cases where different clients 60 | /// use different server IP addresses. This may be necessary when 61 | /// using clients from both inside and outside a local area 62 | /// network. Default is no translation. 63 | /// The key is the IP address returned from friend info requests to other servers. 64 | /// The value is the real IP address used to connect to the server. 65 | pub ip_map: Option>, 66 | 67 | /// UseServicesAlternate determines if the client should use "services-alternate" 68 | /// instead of "services" in info request during cluster tending. 69 | /// "services-alternate" returns server configured external IP addresses that client 70 | /// uses to talk to nodes. "services-alternate" can be used in place of 71 | /// providing a client "ipMap". 72 | /// This feature is recommended instead of using the client-side IpMap above. 73 | /// 74 | /// "services-alternate" is available with Aerospike Server versions >= 3.7.1. 75 | pub use_services_alternate: bool, 76 | 77 | /// Size of the thread pool used in scan and query commands. These commands are often sent to 78 | /// multiple server nodes in parallel threads. A thread pool improves performance because 79 | /// threads do not have to be created/destroyed for each command. 80 | pub thread_pool_size: usize, 81 | 82 | /// Expected cluster name. It not `None`, server nodes must return this cluster name in order 83 | /// to join the client's view of the cluster. Should only be set when connecting to servers 84 | /// that support the "cluster-name" info command. 85 | pub cluster_name: Option, 86 | } 87 | 88 | impl Default for ClientPolicy { 89 | fn default() -> ClientPolicy { 90 | ClientPolicy { 91 | user_password: None, 92 | timeout: Some(Duration::new(30, 0)), 93 | idle_timeout: Some(Duration::new(5, 0)), 94 | max_conns_per_node: 256, 95 | conn_pools_per_node: 1, 96 | fail_if_not_connected: true, 97 | tend_interval: Duration::new(1, 0), 98 | ip_map: None, 99 | use_services_alternate: false, 100 | thread_pool_size: 128, 101 | cluster_name: None, 102 | buffer_reclaim_threshold: 65536, 103 | } 104 | } 105 | } 106 | 107 | impl ClientPolicy { 108 | /// Set username and password to use when authenticating to the cluster. 109 | pub fn set_user_password(&mut self, username: String, password: String) -> Result<()> { 110 | let password = AdminCommand::hash_password(&password)?; 111 | self.user_password = Some((username, password)); 112 | Ok(()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tools/benchmark/src/cli.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | use std::convert::AsRef; 17 | use std::env; 18 | use std::str::FromStr; 19 | 20 | use clap::{App, Arg}; 21 | use num_cpus; 22 | 23 | use workers::Workload; 24 | 25 | const AFTER_HELP: &'static str = r###" 26 | 27 | SETTING SEED HOSTS: 28 | 29 | The list of seed hosts can be specified using -h/--hosts or by 30 | setting the AEROSPIKE_HOSTS environment variable. The format is: 31 | 32 | [:][,[:][,...]] 33 | 34 | If no port is specified, the default port is used. 35 | IPv6 addresses must be enclosed in square brackets. 36 | 37 | SELECTING WORKLOADS 38 | 39 | The -w/--workload parameter is used to select the desired workload for the 40 | benchmark: 41 | 42 | * Insert workload (-w I) 43 | * Read workload (-w RU) 44 | 45 | "###; 46 | 47 | lazy_static! { 48 | pub static ref NUM_CPUS: String = format!("{}", num_cpus::get()); 49 | } 50 | 51 | #[derive(Debug)] 52 | pub struct Options { 53 | pub hosts: String, 54 | pub namespace: String, 55 | pub set: String, 56 | pub keys: i64, 57 | pub start_key: i64, 58 | pub concurrency: i64, 59 | pub workload: Workload, 60 | pub conn_pools_per_node: usize, 61 | } 62 | 63 | pub fn parse_options() -> Options { 64 | let matches = build_cli().get_matches(); 65 | Options { 66 | hosts: matches 67 | .value_of("hosts") 68 | .map(|s| s.to_owned()) 69 | .or_else(|| env::var("AEROSPIKE_HOSTS").ok()) 70 | .unwrap_or_else(|| String::from("127.0.0.1:3000")), 71 | namespace: matches.value_of("namespace").unwrap().to_owned(), 72 | set: matches.value_of("set").unwrap().to_owned(), 73 | keys: i64::from_str(matches.value_of("keys").unwrap()).unwrap(), 74 | start_key: i64::from_str(matches.value_of("startkey").unwrap()).unwrap(), 75 | concurrency: i64::from_str(matches.value_of("concurrency").unwrap()).unwrap(), 76 | workload: Workload::from_str(matches.value_of("workload").unwrap()).unwrap(), 77 | conn_pools_per_node: usize::from_str(matches.value_of("connPoolsPerNode").unwrap()) 78 | .unwrap(), 79 | } 80 | } 81 | 82 | fn build_cli() -> App<'static, 'static> { 83 | App::new(crate_name!()) 84 | .bin_name("benchmark") 85 | .version(crate_version!()) 86 | .about(crate_description!()) 87 | .arg(Arg::from_usage( 88 | "-h, --hosts=[hosts] 'List of seed hosts (see below)'", 89 | )) 90 | .arg(Arg::from_usage("-n, --namespace 'Aerospike namespace'").default_value("test")) 91 | .arg(Arg::from_usage("-s, --set 'Aerospike set name'").default_value("testset")) 92 | .arg( 93 | Arg::from_usage("-k, --keys") 94 | .help( 95 | "Set the number of keys the client is dealing with. If using an 'insert' \ 96 | workload (detailed below), the client will write this number of keys, \ 97 | starting from value = startkey. Otherwise, the client will read and update \ 98 | randomly across the values between startkey and startkey + num_keys. startkey \ 99 | can be set using '-S' or '--startkey'.", 100 | ) 101 | .validator(|val| validate::(val, "Must be number".into())) 102 | .default_value("100000"), 103 | ) 104 | .arg( 105 | Arg::from_usage("-S, --startkey") 106 | .help( 107 | "Set the starting value of the working set of keys. If using an 'insert' \ 108 | workload, the start_value indicates the first value to write. Otherwise, the \ 109 | start_value indicates the smallest value in the working set of keys.", 110 | ) 111 | .validator(|val| validate::(val, "Must be number".into())) 112 | .default_value("0"), 113 | ) 114 | .arg( 115 | Arg::from_usage("-c, --concurrency 'No. threads used to generate load'") 116 | .validator(|val| validate::(val, "Must be number".into())) 117 | .default_value(&*NUM_CPUS), 118 | ) 119 | .arg( 120 | Arg::from_usage( 121 | "-w, --workload 'Workload definition: I | RU (see below for \ 122 | details)'", 123 | ) 124 | .default_value("I"), 125 | ) 126 | .arg( 127 | Arg::from_usage("-Y, --connPoolsPerNode 'Number of connection pools per node'") 128 | .validator(|val| validate::(val, "Must be number".into())) 129 | .default_value("1"), 130 | ) 131 | .after_help(AFTER_HELP.trim()) 132 | } 133 | 134 | fn validate(value: String, err: String) -> Result<(), String> { 135 | match T::from_str(value.as_ref()) { 136 | Ok(_) => Ok(()), 137 | Err(_) => Err(err.into()), 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Aerospike, Inc. 2 | // 3 | // Portions may be licensed to Aerospike, Inc. under one or more contributor 4 | // license agreements. 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | // use this file except in compliance with the License. You may obtain a copy of 8 | // the License at http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations under 14 | // the License. 15 | 16 | //! Error and Result types for the Aerospike client. 17 | //! 18 | //! # Examples 19 | //! 20 | //! Handling an error returned by the client. 21 | //! 22 | //! ```rust 23 | //! use aerospike::*; 24 | //! 25 | //! let hosts = std::env::var("AEROSPIKE_HOSTS").unwrap_or("localhost".into()); 26 | //! let policy = ClientPolicy::default(); 27 | //! let client = Client::new(&policy, &hosts).expect("Failed to connect to cluster"); 28 | //! let key = as_key!("test", "test", "someKey"); 29 | //! match client.get(&ReadPolicy::default(), &key, Bins::None) { 30 | //! Ok(record) => { 31 | //! match record.time_to_live() { 32 | //! None => println!("record never expires"), 33 | //! Some(duration) => println!("ttl: {} secs", duration.as_secs()), 34 | //! } 35 | //! }, 36 | //! Err(Error(ErrorKind::ServerError(ResultCode::KeyNotFoundError), _)) => { 37 | //! println!("No such record: {}", key); 38 | //! }, 39 | //! Err(err) => { 40 | //! println!("Error fetching record: {}", err); 41 | //! for err in err.iter().skip(1) { 42 | //! println!("Caused by: {}", err); 43 | //! } 44 | //! // The backtrace is not always generated. Try to run this example 45 | //! // with `RUST_BACKTRACE=1`. 46 | //! if let Some(backtrace) = err.backtrace() { 47 | //! println!("Backtrace: {:?}", backtrace); 48 | //! } 49 | //! } 50 | //! } 51 | //! ``` 52 | 53 | #![allow(missing_docs)] 54 | 55 | use crate::ResultCode; 56 | 57 | error_chain! { 58 | 59 | // Automatic conversions between this error chain and other error types not defined by the 60 | // `error_chain!`. 61 | foreign_links { 62 | Base64(::base64::DecodeError) 63 | #[doc = "Error decoding Base64 encoded value"]; 64 | InvalidUtf8(::std::str::Utf8Error) 65 | #[doc = "Error interpreting a sequence of u8 as a UTF-8 encoded string."]; 66 | Io(::std::io::Error) 67 | #[doc = "Error during an I/O operation"]; 68 | MpscRecv(::std::sync::mpsc::RecvError) 69 | #[doc = "Error returned from the `recv` function on an MPSC `Receiver`"]; 70 | ParseAddr(::std::net::AddrParseError) 71 | #[doc = "Error parsing an IP or socket address"]; 72 | ParseInt(::std::num::ParseIntError) 73 | #[doc = "Error parsing an integer"]; 74 | PwHash(::pwhash::error::Error) 75 | #[doc = "Error returned while hashing a password for user authentication"]; 76 | } 77 | 78 | // Additional `ErrorKind` variants. 79 | errors { 80 | 81 | /// The client received a server response that it was not able to process. 82 | BadResponse(details: String) { 83 | description("Bad Server Response") 84 | display("Bad Server Response: {}", details) 85 | } 86 | 87 | /// The client was not able to communicate with the cluster due to some issue with the 88 | /// network connection. 89 | Connection(details: String) { 90 | description("Network Connection Issue") 91 | display("Unable to communicate with server cluster: {}", details) 92 | } 93 | 94 | /// One or more of the arguments passed to the client are invalid. 95 | InvalidArgument(details: String) { 96 | description("Invalid Argument") 97 | display("Invalid argument: {}", details) 98 | } 99 | 100 | /// Cluster node is invalid. 101 | InvalidNode(details: String) { 102 | description("Invalid cluster node") 103 | display("Invalid cluster node: {}", details) 104 | } 105 | 106 | /// Exceeded max. number of connections per node. 107 | NoMoreConnections { 108 | description("Too many connections") 109 | display("Too many connections") 110 | } 111 | 112 | /// Server responded with a response code indicating an error condition. 113 | ServerError(rc: ResultCode) { 114 | description("Server Error") 115 | display("Server error: {}", rc.into_string()) 116 | } 117 | 118 | /// Error returned when executing a User-Defined Function (UDF) resulted in an error. 119 | UdfBadResponse(details: String) { 120 | description("UDF Bad Response") 121 | display("UDF Bad Response: {}", details) 122 | } 123 | 124 | /// Error returned when a tasked timeed out before it could be completed. 125 | Timeout(details: String) { 126 | description("Timeout") 127 | display("Timeout: {}", details) 128 | } 129 | } 130 | } 131 | 132 | macro_rules! log_error_chain { 133 | ($err:expr, $($arg:tt)*) => { 134 | error!($($arg)*); 135 | error!("Error: {}", $err); 136 | for e in $err.iter().skip(1) { 137 | error!("caused by: {}", e); 138 | } 139 | if let Some(backtrace) = $err.backtrace() { 140 | error!("backtrace: {:?}", backtrace); 141 | } 142 | }; 143 | } 144 | --------------------------------------------------------------------------------