diff --git a/.travis/aerospike.conf b/.travis/aerospike.conf index 97a9838d..dd22866b 100644 --- a/.travis/aerospike.conf +++ b/.travis/aerospike.conf @@ -50,7 +50,7 @@ network { namespace test { replication-factor 2 memory-size 1G - default-ttl 30d # 30 days, use 0 to never expire/evict. + default-ttl 0 # 30 days, use 0 to never expire/evict. storage-engine memory allow-ttl-without-nsup true } diff --git a/src/commands/buffer.rs b/src/commands/buffer.rs index 083ab1c5..c034b453 100644 --- a/src/commands/buffer.rs +++ b/src/commands/buffer.rs @@ -20,15 +20,14 @@ use byteorder::{ByteOrder, NetworkEndian}; use crate::batch::batch_executor::SharedSlice; use crate::commands::field_type::FieldType; use crate::errors::Result; +use crate::expressions::FilterExpression; use crate::msgpack::encoder; use crate::operations::{Operation, OperationBin, OperationData, OperationType}; use crate::policy::{ BatchPolicy, CommitLevel, ConsistencyLevel, GenerationPolicy, QueryPolicy, ReadPolicy, RecordExistsAction, ScanPolicy, WritePolicy, }; -use crate::query::predexp::PredExp; use crate::{BatchRead, Bin, Bins, CollectionIndexType, Key, Statement, Value}; -use std::sync::Arc; // Contains a read operation. const INFO1_READ: u8 = 1; @@ -164,7 +163,11 @@ impl Buffer { bins: &[A], ) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, policy.send_key)?; + let mut field_count = self.estimate_key_size(key, policy.send_key)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } for bin in bins { self.estimate_operation_size_for_bin(bin.as_ref())?; @@ -180,6 +183,9 @@ impl Buffer { )?; self.write_key(key, policy.send_key)?; + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } for bin in bins { self.write_operation_for_bin(bin.as_ref(), op_type)?; } @@ -190,7 +196,12 @@ impl Buffer { // Writes the command for write operations pub fn set_delete(&mut self, policy: &WritePolicy, key: &Key) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, false)?; + let mut field_count = self.estimate_key_size(key, false)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } + self.size_buffer()?; self.write_header_with_policy( policy, @@ -200,18 +211,31 @@ impl Buffer { 0, )?; self.write_key(key, false)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.end() } // Writes the command for touch operations pub fn set_touch(&mut self, policy: &WritePolicy, key: &Key) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, policy.send_key)?; - + let mut field_count = self.estimate_key_size(key, policy.send_key)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } self.estimate_operation_size()?; self.size_buffer()?; self.write_header_with_policy(policy, 0, INFO2_WRITE, field_count as u16, 1)?; self.write_key(key, policy.send_key)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.write_operation_for_operation_type(OperationType::Touch)?; self.end() } @@ -219,7 +243,12 @@ impl Buffer { // Writes the command for exist operations pub fn set_exists(&mut self, policy: &WritePolicy, key: &Key) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, false)?; + let mut field_count = self.estimate_key_size(key, false)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } + self.size_buffer()?; self.write_header( &policy.base_policy, @@ -229,6 +258,11 @@ impl Buffer { 0, )?; self.write_key(key, false)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.end() } @@ -239,7 +273,11 @@ impl Buffer { Bins::All => self.set_read_for_key_only(policy, key), Bins::Some(ref bin_names) => { self.begin()?; - let field_count = self.estimate_key_size(key, false)?; + let mut field_count = self.estimate_key_size(key, false)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } for bin_name in bin_names { self.estimate_operation_size_for_bin_name(bin_name)?; } @@ -247,9 +285,15 @@ impl Buffer { self.size_buffer()?; self.write_header(policy, INFO1_READ, 0, field_count, bin_names.len() as u16)?; self.write_key(key, false)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + for bin_name in bin_names { self.write_operation_for_bin_name(bin_name, OperationType::Read)?; } + self.end()?; Ok(()) } @@ -259,11 +303,21 @@ impl Buffer { // Writes the command for getting metadata operations pub fn set_read_header(&mut self, policy: &ReadPolicy, key: &Key) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, false)?; + let mut field_count = self.estimate_key_size(key, false)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } + self.estimate_operation_size_for_bin_name("")?; self.size_buffer()?; self.write_header(policy, INFO1_READ | INFO1_NOBINDATA, 0, field_count, 1)?; self.write_key(key, false)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.write_operation_for_bin_name("", OperationType::Read)?; self.end() } @@ -271,11 +325,20 @@ impl Buffer { pub fn set_read_for_key_only(&mut self, policy: &ReadPolicy, key: &Key) -> Result<()> { self.begin()?; - let field_count = self.estimate_key_size(key, false)?; + let mut field_count = self.estimate_key_size(key, false)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } + self.size_buffer()?; self.write_header(policy, INFO1_READ | INFO1_GET_ALL, 0, field_count, 0)?; self.write_key(key, false)?; + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.end() } @@ -286,11 +349,17 @@ impl Buffer { batch_reads: SharedSlice>, offsets: &[usize], ) -> Result<()> { - let field_count = if policy.send_set_name { 2 } else { 1 }; + let field_count_row = if policy.send_set_name { 2 } else { 1 }; self.begin()?; + let mut field_count = 1; self.data_offset += FIELD_HEADER_SIZE as usize + 5; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } + let mut prev: Option<&BatchRead> = None; for idx in offsets { let batch_read: &BatchRead = batch_reads.get(*idx).unwrap(); @@ -324,6 +393,10 @@ impl Buffer { 0, )?; + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + let field_size_offset = self.data_offset; let field_type = if policy.send_set_name { FieldType::BatchIndexWithSet @@ -349,7 +422,7 @@ impl Buffer { match batch_read.bins { Bins::None => { self.write_u8(INFO1_READ | INFO1_NOBINDATA)?; - self.write_u16(field_count)?; + self.write_u16(field_count_row)?; self.write_u16(0)?; self.write_field_string(&key.namespace, FieldType::Namespace)?; if policy.send_set_name { @@ -358,7 +431,7 @@ impl Buffer { } Bins::All => { self.write_u8(INFO1_READ | INFO1_GET_ALL)?; - self.write_u16(field_count)?; + self.write_u16(field_count_row)?; self.write_u16(0)?; self.write_field_string(&key.namespace, FieldType::Namespace)?; if policy.send_set_name { @@ -367,7 +440,7 @@ impl Buffer { } Bins::Some(ref bin_names) => { self.write_u8(INFO1_READ)?; - self.write_u16(field_count)?; + self.write_u16(field_count_row)?; self.write_u16(bin_names.len() as u16)?; self.write_field_string(&key.namespace, FieldType::Namespace)?; if policy.send_set_name { @@ -427,6 +500,10 @@ impl Buffer { | Operation { op: OperationType::BitRead, .. + } + | Operation { + op: OperationType::HllRead, + .. } => read_attr |= INFO1_READ, _ => write_attr |= INFO2_WRITE, } @@ -442,8 +519,11 @@ impl Buffer { self.data_offset += operation.estimate_size()? + OPERATION_HEADER_SIZE as usize; } - let field_count = self.estimate_key_size(key, policy.send_key && write_attr != 0)?; - + let mut field_count = self.estimate_key_size(key, policy.send_key && write_attr != 0)?; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } self.size_buffer()?; if write_attr == 0 { @@ -465,6 +545,10 @@ impl Buffer { } self.write_key(key, policy.send_key && write_attr != 0)?; + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + for operation in operations { operation.write_to(self)?; } @@ -484,11 +568,19 @@ impl Buffer { let mut field_count = self.estimate_key_size(key, policy.send_key)?; field_count += self.estimate_udf_size(package_name, function_name, args)? as u16; - + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } self.size_buffer()?; self.write_header(&policy.base_policy, 0, INFO2_WRITE, field_count, 0)?; self.write_key(key, policy.send_key)?; + + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.write_field_string(package_name, FieldType::UdfPackageName)?; self.write_field_string(function_name, FieldType::UdfFunction)?; self.write_args(args, FieldType::UdfArgList)?; @@ -506,6 +598,10 @@ impl Buffer { self.begin()?; let mut field_count = 0; + let filter_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_size > 0 { + field_count += 1; + } if namespace != "" { self.data_offset += namespace.len() + FIELD_HEADER_SIZE as usize; @@ -562,6 +658,10 @@ impl Buffer { self.write_field_string(set_name, FieldType::Table)?; } + if let Some(filter) = policy.filter_expression() { + self.write_filter_expression(filter, filter_size)?; + } + self.write_field_header(2, FieldType::ScanOptions)?; let mut priority: u8 = policy.base_policy.priority.clone() as u8; @@ -608,7 +708,6 @@ impl Buffer { let mut field_count = 0; let mut filter_size = 0; let mut bin_name_size = 0; - let mut pred_size = 0; if statement.namespace != "" { self.data_offset += statement.namespace.len() + FIELD_HEADER_SIZE as usize; @@ -627,12 +726,6 @@ impl Buffer { } } - if !statement.predexp.is_empty() { - pred_size += self.estimate_predexp_size(statement.predexp.as_slice()); - self.data_offset += pred_size + FIELD_HEADER_SIZE as usize; - field_count += 1; - } - // Allocate space for TaskId field. self.data_offset += 8 + FIELD_HEADER_SIZE as usize; field_count += 1; @@ -665,7 +758,10 @@ impl Buffer { self.data_offset += 2 + FIELD_HEADER_SIZE as usize; field_count += 1; } - + let filter_exp_size = self.estimate_filter_size(policy.filter_expression())?; + if filter_exp_size > 0 { + field_count += 1; + } if let Some(ref aggregation) = statement.aggregation { self.data_offset += 1 + FIELD_HEADER_SIZE as usize; // udf type self.data_offset += aggregation.package_name.len() + FIELD_HEADER_SIZE as usize; @@ -760,8 +856,8 @@ impl Buffer { self.write_u8(100)?; } - if !statement.predexp.is_empty() { - self.write_predexp(statement.predexp.as_slice(), pred_size)?; + if let Some(filter_exp) = policy.filter_expression() { + self.write_filter_expression(filter_exp, filter_exp_size)?; } if let Some(ref aggregation) = statement.aggregation { @@ -793,12 +889,14 @@ impl Buffer { self.end() } - fn estimate_predexp_size(&mut self, predexp: &[Arc>]) -> usize { - let mut size: usize = 0; - for pred in predexp { - size += pred.marshaled_size(); + fn estimate_filter_size(&mut self, filter: &Option) -> Result { + if let Some(filter) = filter { + let filter_size = filter.pack(&mut None)?; + self.data_offset += filter_size + FIELD_HEADER_SIZE as usize; + Ok(filter_size) + } else { + Ok(0) } - size } fn estimate_key_size(&mut self, key: &Key, send_key: bool) -> Result { @@ -991,6 +1089,13 @@ impl Buffer { Ok(()) } + fn write_filter_expression(&mut self, filter: &FilterExpression, size: usize) -> Result<()> { + self.write_field_header(size, FieldType::FilterExp)?; + filter.pack(&mut Some(self))?; + + Ok(()) + } + fn write_field_header(&mut self, size: usize, ftype: FieldType) -> Result<()> { self.write_i32(size as i32 + 1)?; self.write_u8(ftype as u8)?; @@ -1020,16 +1125,6 @@ impl Buffer { Ok(()) } - fn write_predexp(&mut self, predexp: &[Arc>], size: usize) -> Result<()> { - self.write_field_header(size, FieldType::Predicate)?; - for pred in predexp { - // PredExp structs hold their own write function, varying on the predexp type. - pred.write(self)?; - } - - Ok(()) - } - fn write_args(&mut self, args: Option<&[Value]>, ftype: FieldType) -> Result<()> { if let Some(args) = args { self.write_field_header(encoder::pack_array(&mut None, args)?, ftype)?; diff --git a/src/commands/field_type.rs b/src/commands/field_type.rs index c777943d..6a7f4c0b 100644 --- a/src/commands/field_type.rs +++ b/src/commands/field_type.rs @@ -40,5 +40,5 @@ pub enum FieldType { QueryBinList = 40, BatchIndex = 41, BatchIndexWithSet = 42, - Predicate = 43, + FilterExp = 43, } diff --git a/src/commands/particle_type.rs b/src/commands/particle_type.rs index 5c0c2cbd..c88260fc 100644 --- a/src/commands/particle_type.rs +++ b/src/commands/particle_type.rs @@ -14,6 +14,7 @@ // the License. #[derive(Debug, Clone)] +#[doc(hidden)] pub enum ParticleType { // Server particle types. Unsupported types are commented out. NULL = 0, @@ -35,6 +36,7 @@ pub enum ParticleType { // RTA_APPEND_DICT = 16, // RTA_APPEND_LIST = 17, // LUA_BLOB = 18, + HLL = 18, MAP = 19, LIST = 20, LDT = 21, @@ -63,6 +65,7 @@ impl From for ParticleType { // 16 => ParticleType::RTA_APPEND_DICT, // 17 => ParticleType::RTA_APPEND_LIST, // 18 => ParticleType::LUA_BLOB , + 18 => ParticleType::HLL, 19 => ParticleType::MAP, 20 => ParticleType::LIST, 21 => ParticleType::LDT, diff --git a/src/expressions/bitwise.rs b/src/expressions/bitwise.rs new file mode 100644 index 00000000..3350899d --- /dev/null +++ b/src/expressions/bitwise.rs @@ -0,0 +1,608 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! Bitwise Aerospike Filter Expressions. +use crate::expressions::{ExpOp, ExpType, ExpressionArgument, FilterExpression, MODIFY}; +use crate::operations::bitwise::{BitPolicy, BitwiseOverflowActions, BitwiseResizeFlags}; +use crate::Value; + +const MODULE: i64 = 1; +const INT_FLAGS_SIGNED: i64 = 1; + +#[doc(hidden)] +pub enum BitExpOp { + Resize = 0, + Insert = 1, + Remove = 2, + Set = 3, + Or = 4, + Xor = 5, + And = 6, + Not = 7, + LShift = 8, + RShift = 9, + Add = 10, + Subtract = 11, + SetInt = 12, + Get = 50, + Count = 51, + LScan = 52, + RScan = 53, + GetInt = 54, +} + +/// Create expression that resizes byte[] to byteSize according to resizeFlags +/// and returns byte[]. +/// +/// ``` +/// // Resize bin "a" and compare bit count +/// // bin = [0b00000001, 0b01000010] +/// // byteSize = 4 +/// // resizeFlags = 0 +/// // returns [0b00000001, 0b01000010, 0b00000000, 0b00000000] +/// use aerospike::operations::bitwise::{BitPolicy, BitwiseResizeFlags}; +/// use aerospike::expressions::{eq, int_val, blob_bin}; +/// use aerospike::expressions::bitwise::{count, resize}; +/// eq( +/// count(int_val(0), int_val(3), +/// resize(&BitPolicy::default(), int_val(4), BitwiseResizeFlags::Default, blob_bin("a".to_string()))), +/// int_val(2)); +/// ``` +pub fn resize( + policy: &BitPolicy, + byte_size: FilterExpression, + resize_flags: BitwiseResizeFlags, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Resize as i64)), + ExpressionArgument::FilterExpression(byte_size), + ExpressionArgument::Value(Value::from(policy.flags)), + ExpressionArgument::Value(Value::from(resize_flags as u8)), + ]; + add_write(bin, args) +} + +/// Create expression that inserts value bytes into byte[] bin at byteOffset and returns byte[]. +/// +/// +/// ``` +/// // Insert bytes into bin "a" and compare bit count +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // byteOffset = 1 +/// // value = [0b11111111, 0b11000111] +/// // bin result = [0b00000001, 0b11111111, 0b11000111, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// use aerospike::operations::bitwise::BitPolicy; +/// use aerospike::expressions::{eq, int_val, blob_val, blob_bin}; +/// use aerospike::expressions::bitwise::{count, insert}; +/// let bytes: Vec = vec![]; +/// eq( +/// count(int_val(0), int_val(3), +/// insert(&BitPolicy::default(), int_val(1), blob_val(bytes), blob_bin("a".to_string()))), +/// int_val(2)); +/// ``` +pub fn insert( + policy: &BitPolicy, + byte_offset: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Insert as i64)), + ExpressionArgument::FilterExpression(byte_offset), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that removes bytes from byte[] bin at byteOffset for byteSize and returns byte[]. +/// +/// ``` +/// // Remove bytes from bin "a" and compare bit count +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // byteOffset = 2 +/// // byteSize = 3 +/// // bin result = [0b00000001, 0b01000010] +/// use aerospike::expressions::{eq, int_val, blob_bin}; +/// use aerospike::operations::bitwise::BitPolicy; +/// use aerospike::expressions::bitwise::{count, remove}; +/// eq( +/// count(int_val(0), int_val(3), +/// remove(&BitPolicy::default(), int_val(2), int_val(3), blob_bin("a".to_string()))), +/// int_val(2)); +/// ``` +pub fn remove( + policy: &BitPolicy, + byte_offset: FilterExpression, + byte_size: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Remove as i64)), + ExpressionArgument::FilterExpression(byte_offset), + ExpressionArgument::FilterExpression(byte_size), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that sets value on byte[] bin at bitOffset for bitSize and returns byte[]. +/// +/// ``` +/// // Set bytes in bin "a" and compare bit count +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 13 +/// // bitSize = 3 +/// // value = [0b11100000] +/// // bin result = [0b00000001, 0b01000111, 0b00000011, 0b00000100, 0b00000101] +/// use aerospike::operations::bitwise::BitPolicy; +/// use aerospike::expressions::{eq, int_val, blob_val, blob_bin}; +/// use aerospike::expressions::bitwise::{count, set}; +/// let bytes: Vec = vec![]; +/// eq( +/// count(int_val(0), int_val(3), +/// set(&BitPolicy::default(), int_val(13), int_val(3), blob_val(bytes), blob_bin("a".to_string()))), +/// int_val(2)); +/// ``` +pub fn set( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Set as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that performs bitwise "or" on value and byte[] bin at bitOffset for bitSize +/// and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 17 +/// bitSize = 6 +/// value = [0b10101000] +/// bin result = [0b00000001, 0b01000010, 0b01010111, 0b00000100, 0b00000101] +/// ``` +pub fn or( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Or as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that performs bitwise "xor" on value and byte[] bin at bitOffset for bitSize +/// and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 17 +/// bitSize = 6 +/// value = [0b10101100] +/// bin result = [0b00000001, 0b01000010, 0b01010101, 0b00000100, 0b00000101] +/// ``` +pub fn xor( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Xor as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that performs bitwise "and" on value and byte[] bin at bitOffset for bitSize +/// and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 23 +/// bitSize = 9 +/// value = [0b00111100, 0b10000000] +/// bin result = [0b00000001, 0b01000010, 0b00000010, 0b00000000, 0b00000101] +/// ``` +pub fn and( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::And as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that negates byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 25 +/// bitSize = 6 +/// bin result = [0b00000001, 0b01000010, 0b00000011, 0b01111010, 0b00000101] +/// ``` +pub fn not( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Not as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that shifts left byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 32 +/// bitSize = 8 +/// shift = 3 +/// bin result = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00101000] +/// ``` +pub fn lshift( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + shift: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::LShift as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(shift), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that shifts right byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 0 +/// bitSize = 9 +/// shift = 1 +/// bin result = [0b00000000, 0b11000010, 0b00000011, 0b00000100, 0b00000101] +/// ``` +pub fn rshift( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + shift: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::RShift as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(shift), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that adds value to byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// `BitSize` must be <= 64. Signed indicates if bits should be treated as a signed number. +/// If add overflows/underflows, `BitwiseOverflowActions` is used. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 24 +/// bitSize = 16 +/// value = 128 +/// signed = false +/// bin result = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b10000101] +/// ``` +pub fn add( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + signed: bool, + action: BitwiseOverflowActions, + bin: FilterExpression, +) -> FilterExpression { + let mut flags = action as u8; + if signed { + flags |= INT_FLAGS_SIGNED as u8; + } + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Add as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ExpressionArgument::Value(Value::from(flags)), + ]; + add_write(bin, args) +} + +/// Create expression that subtracts value from byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// `BitSize` must be <= 64. Signed indicates if bits should be treated as a signed number. +/// If add overflows/underflows, `BitwiseOverflowActions` is used. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 24 +/// bitSize = 16 +/// value = 128 +/// signed = false +/// bin result = [0b00000001, 0b01000010, 0b00000011, 0b0000011, 0b10000101] +/// ``` +pub fn subtract( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + signed: bool, + action: BitwiseOverflowActions, + bin: FilterExpression, +) -> FilterExpression { + let mut flags = action as u8; + if signed { + flags |= INT_FLAGS_SIGNED as u8; + } + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Subtract as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ExpressionArgument::Value(Value::from(flags)), + ]; + add_write(bin, args) +} + +/// Create expression that sets value to byte[] bin starting at bitOffset for bitSize and returns byte[]. +/// `BitSize` must be <= 64. +/// +/// ```text +/// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// bitOffset = 1 +/// bitSize = 8 +/// value = 127 +/// bin result = [0b00111111, 0b11000010, 0b00000011, 0b0000100, 0b00000101] +/// ``` +pub fn set_int( + policy: &BitPolicy, + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::SetInt as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags)), + ]; + add_write(bin, args) +} + +/// Create expression that returns bits from byte[] bin starting at bitOffset for bitSize. +/// +/// ``` +/// // Bin "a" bits = [0b10000000] +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 9 +/// // bitSize = 5 +/// // returns [0b10000000] +/// +/// use aerospike::expressions::{eq, int_val, blob_bin, blob_val}; +/// use aerospike::expressions::bitwise::get; +/// eq( +/// get(int_val(9), int_val(5), blob_bin("a".to_string())), +/// blob_val(vec![0b10000000])); +/// ``` +pub fn get( + bit_offset: FilterExpression, + bit_size: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Get as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ]; + add_read(bin, ExpType::BLOB, args) +} + +/// Create expression that returns integer count of set bits from byte[] bin starting at +/// bitOffset for bitSize. +/// +/// ``` +/// // Bin "a" bit count <= 2 +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 20 +/// // bitSize = 4 +/// // returns 2 +/// +/// use aerospike::expressions::{le, int_val, blob_bin}; +/// use aerospike::expressions::bitwise::count; +/// le(count(int_val(0), int_val(5), blob_bin("a".to_string())), int_val(2)); +/// ``` +pub fn count( + bit_offset: FilterExpression, + bit_size: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::Count as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ]; + add_read(bin, ExpType::INT, args) +} + +/// Create expression that returns integer bit offset of the first specified value bit in byte[] bin +/// starting at bitOffset for bitSize. +/// +/// ``` +/// // lscan(a) == 5 +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 24 +/// // bitSize = 8 +/// // value = true +/// // returns 5 +/// use aerospike::expressions::{eq, int_val, blob_bin}; +/// use aerospike::expressions::bitwise::lscan; +/// eq(lscan(int_val(24), int_val(8), int_val(1), blob_bin("a".to_string())), int_val(5)); +/// ``` +/// +pub fn lscan( + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::LScan as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ]; + add_read(bin, ExpType::INT, args) +} + +/// Create expression that returns integer bit offset of the last specified value bit in byte[] bin +/// starting at bitOffset for bitSize. +/// Example: +/// +/// ``` +/// // rscan(a) == 7 +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 32 +/// // bitSize = 8 +/// // value = true +/// // returns 7 +/// +/// use aerospike::expressions::{eq, int_val, blob_bin}; +/// use aerospike::expressions::bitwise::rscan; +/// eq(rscan(int_val(32), int_val(8), int_val(1), blob_bin("a".to_string())), int_val(7)); +/// ``` +/// +pub fn rscan( + bit_offset: FilterExpression, + bit_size: FilterExpression, + value: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::RScan as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ExpressionArgument::FilterExpression(value), + ]; + add_read(bin, ExpType::INT, args) +} + +/// Create expression that returns integer from byte[] bin starting at bitOffset for bitSize. +/// Signed indicates if bits should be treated as a signed number. +/// +/// ``` +/// // getInt(a) == 16899 +/// // bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// // bitOffset = 8 +/// // bitSize = 16 +/// // signed = false +/// // returns 16899 +/// use aerospike::expressions::{eq, int_val, blob_bin}; +/// use aerospike::expressions::bitwise::get_int; +/// eq(get_int(int_val(8), int_val(16), false, blob_bin("a".to_string())), int_val(16899)); +/// ``` +pub fn get_int( + bit_offset: FilterExpression, + bit_size: FilterExpression, + signed: bool, + bin: FilterExpression, +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Value(Value::from(BitExpOp::GetInt as i64)), + ExpressionArgument::FilterExpression(bit_offset), + ExpressionArgument::FilterExpression(bit_size), + ]; + if signed { + args.push(ExpressionArgument::Value(Value::from(INT_FLAGS_SIGNED))); + } + add_read(bin, ExpType::INT, args) +} + +#[doc(hidden)] +fn add_write(bin: FilterExpression, arguments: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE | MODIFY), + module: Some(ExpType::BLOB), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn add_read( + bin: FilterExpression, + return_type: ExpType, + arguments: Vec, +) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} diff --git a/src/expressions/hll.rs b/src/expressions/hll.rs new file mode 100644 index 00000000..065ffded --- /dev/null +++ b/src/expressions/hll.rs @@ -0,0 +1,289 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! HLL Aerospike Filter Expressions. + +use crate::expressions::{int_val, ExpOp, ExpType, ExpressionArgument, FilterExpression, MODIFY}; +use crate::operations::hll::HLLPolicy; +use crate::Value; + +const MODULE: i64 = 2; + +#[doc(hidden)] +pub enum HllExpOp { + Add = 1, + Count = 50, + Union = 51, + UnionCount = 52, + IntersectCount = 53, + Similarity = 54, + Describe = 55, + MayContain = 56, +} + +/// Create expression that adds list values to a HLL set and returns HLL set. +/// The function assumes HLL bin already exists. +/// ``` +/// use aerospike::operations::hll::HLLPolicy; +/// use aerospike::Value; +/// use aerospike::expressions::{gt, list_val, hll_bin, int_val}; +/// use aerospike::expressions::hll::add; +/// +/// // Add values to HLL bin "a" and check count > 7 +/// let list = vec![Value::from(1)]; +/// gt(add(HLLPolicy::default(), list_val(list), hll_bin("a".to_string())), int_val(7)); +/// ``` +pub fn add(policy: HLLPolicy, list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_with_index_and_min_hash(policy, list, int_val(-1), int_val(-1), bin) +} + +/// Create expression that adds values to a HLL set and returns HLL set. +/// If HLL bin does not exist, use `indexBitCount` to create HLL bin. +/// ``` +/// use aerospike::operations::hll::HLLPolicy; +/// use aerospike::Value; +/// use aerospike::expressions::{gt, list_val, int_val, hll_bin}; +/// use aerospike::expressions::hll::add_with_index; +/// +/// // Add values to HLL bin "a" and check count > 7 +/// let list = vec![Value::from(1)]; +/// gt(add_with_index(HLLPolicy::default(), list_val(list), int_val(10), hll_bin("a".to_string())), int_val(7)); +/// ``` +pub fn add_with_index( + policy: HLLPolicy, + list: FilterExpression, + index_bit_count: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + add_with_index_and_min_hash(policy, list, index_bit_count, int_val(-1), bin) +} + +/// Create expression that adds values to a HLL set and returns HLL set. If HLL bin does not +/// exist, use `indexBitCount` and `minHashBitCount` to create HLL set. +/// ``` +/// use aerospike::expressions::{gt, list_val, int_val, hll_bin}; +/// use aerospike::operations::hll::HLLPolicy; +/// use aerospike::Value; +/// use aerospike::expressions::hll::add_with_index_and_min_hash; +/// +/// // Add values to HLL bin "a" and check count > 7 +/// let list = vec![Value::from(1)]; +/// gt(add_with_index_and_min_hash(HLLPolicy::default(), list_val(list), int_val(10), int_val(20), hll_bin("a".to_string())), int_val(7)); +/// ``` +pub fn add_with_index_and_min_hash( + policy: HLLPolicy, + list: FilterExpression, + index_bit_count: FilterExpression, + min_hash_count: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + add_write( + bin, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::Add as i64)), + ExpressionArgument::FilterExpression(list), + ExpressionArgument::FilterExpression(index_bit_count), + ExpressionArgument::FilterExpression(min_hash_count), + ExpressionArgument::Value(Value::from(policy.flags as i64)), + ], + ) +} + +/// Create expression that returns estimated number of elements in the HLL bin. +/// +/// ``` +/// // HLL bin "a" count > 7 +/// use aerospike::expressions::{gt, hll_bin, int_val}; +/// use aerospike::expressions::hll::get_count; +/// gt(get_count(hll_bin("a".to_string())), int_val(7)); +/// ``` +pub fn get_count(bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::INT, + vec![ExpressionArgument::Value(Value::from( + HllExpOp::Count as i64, + ))], + ) +} + +/// Create expression that returns a HLL object that is the union of all specified HLL objects +/// in the list with the HLL bin. +/// +/// ``` +/// use aerospike::expressions::hll::get_union; +/// use aerospike::expressions::{hll_bin, blob_val}; +/// +/// // Union of HLL bins "a" and "b" +/// get_union(hll_bin("a".to_string()), hll_bin("b".to_string())); +/// +/// // Union of local HLL list with bin "b" +/// let blob: Vec = vec![]; +/// get_union(hll_bin("b".to_string()), blob_val(blob)); +/// ``` +pub fn get_union(list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::HLL, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::Union as i64)), + ExpressionArgument::FilterExpression(list), + ], + ) +} + +/// Create expression that returns estimated number of elements that would be contained by +/// the union of these HLL objects. +/// +/// ``` +/// use aerospike::expressions::hll::get_union_count; +/// use aerospike::expressions::{hll_bin, blob_val}; +/// +/// // Union count of HLL bins "a" and "b" +/// get_union_count(hll_bin("a".to_string()), hll_bin("b".to_string())); +/// +/// // Union count of local HLL list with bin "b" +/// let blob: Vec = vec![]; +/// get_union_count(hll_bin("b".to_string()), blob_val(blob)); +/// ``` +pub fn get_union_count(list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::INT, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::UnionCount as i64)), + ExpressionArgument::FilterExpression(list), + ], + ) +} + +/// Create expression that returns estimated number of elements that would be contained by +/// the intersection of these HLL objects. +/// +/// ``` +/// use aerospike::expressions::{hll_bin, blob_val}; +/// use aerospike::expressions::hll::get_union_count; +/// +/// // Intersect count of HLL bins "a" and "b" +/// get_union_count(hll_bin("a".to_string()), hll_bin("b".to_string())); +/// +/// // Intersect count of local HLL list with bin "b" +/// let blob: Vec = vec![]; +/// get_union_count(hll_bin("b".to_string()), blob_val(blob)); +/// ``` +pub fn get_intersect_count(list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::INT, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::IntersectCount as i64)), + ExpressionArgument::FilterExpression(list), + ], + ) +} + +/// Create expression that returns estimated similarity of these HLL objects as a 64 bit float. +/// +/// ``` +/// use aerospike::expressions::{hll_bin, ge, float_val}; +/// use aerospike::expressions::hll::get_similarity; +/// +/// // Similarity of HLL bins "a" and "b" >= 0.75 +/// ge(get_similarity(hll_bin("a".to_string()), hll_bin("b".to_string())), float_val(0.75)); +/// ``` +pub fn get_similarity(list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::FLOAT, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::Similarity as i64)), + ExpressionArgument::FilterExpression(list), + ], + ) +} + +/// Create expression that returns `indexBitCount` and `minHashBitCount` used to create HLL bin +/// in a list of longs. `list[0]` is `indexBitCount` and `list[1]` is `minHashBitCount`. +/// +/// ``` +/// use aerospike::expressions::{ExpType, lt, int_val, hll_bin}; +/// use aerospike::expressions::lists::{get_by_index}; +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::hll::describe; +/// +/// // Bin "a" `indexBitCount` < 10 +/// lt(get_by_index(ListReturnType::Values, ExpType::INT, int_val(0), describe(hll_bin("a".to_string())), &[]), int_val(10)); +/// ``` +pub fn describe(bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::LIST, + vec![ExpressionArgument::Value(Value::from( + HllExpOp::Describe as i64, + ))], + ) +} + +/// Create expression that returns one if HLL bin may contain all items in the list. +/// +/// ``` +/// use aerospike::Value; +/// use aerospike::expressions::{eq, list_val, hll_bin, int_val}; +/// use aerospike::expressions::hll::may_contain; +/// let list: Vec = vec![Value::from("x")]; +/// +/// // Bin "a" may contain value "x" +/// eq(may_contain(list_val(list), hll_bin("a".to_string())), int_val(1)); +/// ``` +pub fn may_contain(list: FilterExpression, bin: FilterExpression) -> FilterExpression { + add_read( + bin, + ExpType::INT, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::MayContain as i64)), + ExpressionArgument::FilterExpression(list), + ], + ) +} + +#[doc(hidden)] +fn add_read( + bin: FilterExpression, + return_type: ExpType, + arguments: Vec, +) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn add_write(bin: FilterExpression, arguments: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE | MODIFY), + module: Some(ExpType::HLL), + exps: None, + arguments: Some(arguments), + } +} diff --git a/src/expressions/lists.rs b/src/expressions/lists.rs new file mode 100644 index 00000000..475fb548 --- /dev/null +++ b/src/expressions/lists.rs @@ -0,0 +1,696 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! List Cdt Aerospike Filter Expressions. + +use crate::expressions::{nil, ExpOp, ExpType, ExpressionArgument, FilterExpression, MODIFY}; +use crate::operations::cdt_context::{CdtContext, CtxType}; +use crate::operations::lists::{CdtListOpType, ListPolicy, ListReturnType, ListSortFlags}; +use crate::Value; + +const MODULE: i64 = 0; +/// Create expression that appends value to end of list. +pub fn append( + policy: ListPolicy, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Append as i64)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.attributes as u8)), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that appends list items to end of list. +pub fn append_items( + policy: ListPolicy, + list: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::AppendItems as i64)), + ExpressionArgument::FilterExpression(list), + ExpressionArgument::Value(Value::from(policy.attributes as u8)), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that inserts value to specified index of list. +pub fn insert( + policy: ListPolicy, + index: FilterExpression, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Insert as i64)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that inserts each input list item starting at specified index of list. +pub fn insert_items( + policy: ListPolicy, + index: FilterExpression, + list: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::InsertItems as i64)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(list), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that increments `list[index]` by value. +/// Value expression should resolve to a number. +pub fn increment( + policy: ListPolicy, + index: FilterExpression, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Increment as i64)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.attributes as u8)), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that sets item value at specified index in list. +pub fn set( + policy: ListPolicy, + index: FilterExpression, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Set as i64)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes all items in list. +pub fn clear(bin: FilterExpression, ctx: &[CdtContext]) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Clear as i64)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that sorts list according to sortFlags. +pub fn sort( + sort_flags: ListSortFlags, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Sort as i64)), + ExpressionArgument::Value(Value::from(sort_flags as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items identified by value. +pub fn remove_by_value( + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByValue as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items identified by values. +pub fn remove_by_value_list( + values: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByValueList as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(values), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items identified by value range (valueBegin inclusive, valueEnd exclusive). +/// If valueBegin is null, the range is less than valueEnd. If valueEnd is null, the range is +/// greater than equal to valueBegin. +pub fn remove_by_value_range( + value_begin: Option, + value_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByValueInterval as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ]; + if let Some(val_beg) = value_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = value_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_write(bin, ctx, args) +} + +/// Create expression that removes list items nearest to value and greater by relative rank. +/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text +/// (value,rank) = [removed items] +/// (5,0) = [5,9,11,15] +/// (5,1) = [9,11,15] +/// (5,-1) = [4,5,9,11,15] +/// (3,0) = [4,5,9,11,15] +/// (3,3) = [11,15] +/// (3,-3) = [0,4,5,9,11,15] +/// ``` +pub fn remove_by_value_relative_rank_range( + value: FilterExpression, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByValueRelRankRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items nearest to value and greater by relative rank with a count limit. +/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text +/// (value,rank,count) = [removed items] +/// (5,0,2) = [5,9] +/// (5,1,1) = [9] +/// (5,-1,2) = [4,5] +/// (3,0,1) = [4] +/// (3,3,7) = [11,15] +/// (3,-3,2) = [] +/// ``` +pub fn remove_by_value_relative_rank_range_count( + value: FilterExpression, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByValueRelRankRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list item identified by index. +pub fn remove_by_index( + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByIndex as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items starting at specified index to the end of list. +pub fn remove_by_index_range( + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByIndexRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes "count" list items starting at specified index. +pub fn remove_by_index_range_count( + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByIndexRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list item identified by rank. +pub fn remove_by_rank( + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByRank as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes list items starting at specified rank to the last ranked item. +pub fn remove_by_rank_range( + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByRankRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes "count" list items starting at specified rank. +pub fn remove_by_rank_range_count( + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::RemoveByRankRange as i64)), + ExpressionArgument::Value(Value::from(ListReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that returns list size. +/// +/// ``` +/// // List bin "a" size > 7 +/// use aerospike::expressions::{gt, list_bin, int_val}; +/// use aerospike::expressions::lists::size; +/// gt(size(list_bin("a".to_string()), &[]), int_val(7)); +/// ``` +pub fn size(bin: FilterExpression, ctx: &[CdtContext]) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::Size as i64)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, ExpType::INT, args) +} + +/// Create expression that selects list items identified by value and returns selected +/// data specified by returnType. +/// +/// ``` +/// // List bin "a" contains at least one item == "abc" +/// use aerospike::expressions::{gt, string_val, list_bin, int_val}; +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::lists::get_by_value; +/// gt( +/// get_by_value(ListReturnType::Count, string_val("abc".to_string()), list_bin("a".to_string()), &[]), +/// int_val(0)); +/// ``` +/// +pub fn get_by_value( + return_type: ListReturnType, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByValue as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list items identified by value range and returns selected data +/// specified by returnType. +/// +/// ``` +/// // List bin "a" items >= 10 && items < 20 +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::lists::get_by_value_range; +/// use aerospike::expressions::{int_val, list_bin}; +/// +/// get_by_value_range(ListReturnType::Values, Some(int_val(10)), Some(int_val(20)), list_bin("a".to_string()), &[]); +/// ``` +pub fn get_by_value_range( + return_type: ListReturnType, + value_begin: Option, + value_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtListOpType::GetByValueInterval as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ]; + if let Some(val_beg) = value_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = value_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list items identified by values and returns selected data +/// specified by returnType. +pub fn get_by_value_list( + return_type: ListReturnType, + values: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByValueList as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(values), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list items nearest to value and greater by relative rank +/// and returns selected data specified by returnType. +/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text +/// (value,rank) = [selected items] +/// (5,0) = [5,9,11,15] +/// (5,1) = [9,11,15] +/// (5,-1) = [4,5,9,11,15] +/// (3,0) = [4,5,9,11,15] +/// (3,3) = [11,15] +/// (3,-3) = [0,4,5,9,11,15] +/// ``` +pub fn get_by_value_relative_rank_range( + return_type: ListReturnType, + value: FilterExpression, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByValueRelRankRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list items nearest to value and greater by relative rank with a count limit +/// and returns selected data specified by returnType. +/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text +/// (value,rank,count) = [selected items] +/// (5,0,2) = [5,9] +/// (5,1,1) = [9] +/// (5,-1,2) = [4,5] +/// (3,0,1) = [4] +/// (3,3,7) = [11,15] +/// (3,-3,2) = [] +/// ``` +pub fn get_by_value_relative_rank_range_count( + return_type: ListReturnType, + value: FilterExpression, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByValueRelRankRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list item identified by index and returns +/// selected data specified by returnType. +/// +/// ``` +/// // a[3] == 5 +/// use aerospike::expressions::{ExpType, eq, int_val, list_bin}; +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::lists::get_by_index; +/// eq( +/// get_by_index(ListReturnType::Values, ExpType::INT, int_val(3), list_bin("a".to_string()), &[]), +/// int_val(5)); +/// ``` +/// +pub fn get_by_index( + return_type: ListReturnType, + value_type: ExpType, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByIndex as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, value_type, args) +} + +/// Create expression that selects list items starting at specified index to the end of list +/// and returns selected data specified by returnType . +pub fn get_by_index_range( + return_type: ListReturnType, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByIndexRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects "count" list items starting at specified index +/// and returns selected data specified by returnType. +pub fn get_by_index_range_count( + return_type: ListReturnType, + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByIndexRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects list item identified by rank and returns selected +/// data specified by returnType. +/// +/// ``` +/// // Player with lowest score. +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::{ExpType, int_val, list_bin}; +/// use aerospike::expressions::lists::get_by_rank; +/// get_by_rank(ListReturnType::Values, ExpType::STRING, int_val(0), list_bin("a".to_string()), &[]); +/// ``` +pub fn get_by_rank( + return_type: ListReturnType, + value_type: ExpType, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByRank as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, value_type, args) +} + +/// Create expression that selects list items starting at specified rank to the last ranked item +/// and returns selected data specified by returnType. +pub fn get_by_rank_range( + return_type: ListReturnType, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByRankRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects "count" list items starting at specified rank and returns +/// selected data specified by returnType. +pub fn get_by_rank_range_count( + return_type: ListReturnType, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtListOpType::GetByRankRange as i64)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +#[doc(hidden)] +fn add_read( + bin: FilterExpression, + return_type: ExpType, + arguments: Vec, +) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn add_write( + bin: FilterExpression, + ctx: &[CdtContext], + arguments: Vec, +) -> FilterExpression { + let return_type: ExpType; + if ctx.is_empty() { + return_type = ExpType::LIST + } else if (ctx[0].id & CtxType::ListIndex as u8) == 0 { + return_type = ExpType::MAP; + } else { + return_type = ExpType::LIST; + } + + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE | MODIFY), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn get_value_type(return_type: ListReturnType) -> ExpType { + if (return_type as u8 & !(ListReturnType::Inverted as u8)) == ListReturnType::Values as u8 { + ExpType::LIST + } else { + ExpType::INT + } +} diff --git a/src/expressions/maps.rs b/src/expressions/maps.rs new file mode 100644 index 00000000..b872fd89 --- /dev/null +++ b/src/expressions/maps.rs @@ -0,0 +1,842 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! Map Cdt Aerospike Filter Expressions. +use crate::expressions::{nil, ExpOp, ExpType, ExpressionArgument, FilterExpression, MODIFY}; +use crate::operations::cdt_context::{CdtContext, CtxType}; +use crate::operations::maps::{map_write_op, CdtMapOpType}; +use crate::{MapPolicy, MapReturnType, Value}; + +#[doc(hidden)] +const MODULE: i64 = 0; + +/// Create expression that writes key/value item to map bin. +pub fn put( + policy: &MapPolicy, + key: FilterExpression, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args: Vec; + let op = map_write_op(policy, false); + if op as u8 == CdtMapOpType::Replace as u8 { + args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(op as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(value), + ] + } else { + args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(op as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Value(Value::from(policy.order as u8)), + ] + } + add_write(bin, ctx, args) +} + +/// Create expression that writes each map item to map bin. +pub fn put_items( + policy: &MapPolicy, + map: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args: Vec; + let op = map_write_op(policy, true); + if op as u8 == CdtMapOpType::Replace as u8 { + args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(op as u8)), + ExpressionArgument::FilterExpression(map), + ] + } else { + args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(op as u8)), + ExpressionArgument::FilterExpression(map), + ExpressionArgument::Value(Value::from(policy.order as u8)), + ] + } + add_write(bin, ctx, args) +} + +/// Create expression that increments values by incr for all items identified by key. +/// Valid only for numbers. +pub fn increment( + policy: &MapPolicy, + key: FilterExpression, + incr: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::Increment as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(incr), + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(policy.order as u8)), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes all items in map. +pub fn clear(bin: FilterExpression, ctx: &[CdtContext]) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::Clear as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map item identified by key. +pub fn remove_by_key( + key: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByKey as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items identified by keys. +pub fn remove_by_key_list( + keys: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveKeyList as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(keys), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items identified by key range (keyBegin inclusive, keyEnd exclusive). +/// If keyBegin is null, the range is less than keyEnd. +/// If keyEnd is null, the range is greater than equal to keyBegin. +pub fn remove_by_key_range( + key_begin: Option, + key_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByKeyInterval as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ]; + if let Some(val_beg) = key_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = key_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_write(bin, ctx, args) +} + +/// Create expression that removes map items nearest to key and greater by index. +/// +/// Examples for map [{0=17},{4=2},{5=15},{9=10}]: +/// +/// * (value,index) = [removed items] +/// * (5,0) = [{5=15},{9=10}] +/// * (5,1) = [{9=10}] +/// * (5,-1) = [{4=2},{5=15},{9=10}] +/// * (3,2) = [{9=10}] +/// * (3,-2) = [{0=17},{4=2},{5=15},{9=10}] +pub fn remove_by_key_relative_index_range( + key: FilterExpression, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByKeyRelIndexRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items nearest to key and greater by index with a count limit. +/// +/// Examples for map [{0=17},{4=2},{5=15},{9=10}]: +/// +/// (value,index,count) = [removed items] +/// * (5,0,1) = [{5=15}] +/// * (5,1,2) = [{9=10}] +/// * (5,-1,1) = [{4=2}] +/// * (3,2,1) = [{9=10}] +/// * (3,-2,2) = [{0=17}] +pub fn remove_by_key_relative_index_range_count( + key: FilterExpression, + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByKeyRelIndexRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items identified by value. +pub fn remove_by_value( + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByValue as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items identified by values. +pub fn remove_by_value_list( + values: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveValueList as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(values), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items identified by value range (valueBegin inclusive, valueEnd exclusive). +/// If valueBegin is null, the range is less than valueEnd. +/// If valueEnd is null, the range is greater than equal to valueBegin. +pub fn remove_by_value_range( + value_begin: Option, + value_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByValueInterval as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ]; + if let Some(val_beg) = value_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = value_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_write(bin, ctx, args) +} + +/// Create expression that removes map items nearest to value and greater by relative rank. +/// +/// Examples for map [{4=2},{9=10},{5=15},{0=17}]: +/// +/// * (value,rank) = [removed items] +/// * (11,1) = [{0=17}] +/// * (11,-1) = [{9=10},{5=15},{0=17}] +pub fn remove_by_value_relative_rank_range( + value: FilterExpression, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByValueRelRankRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items nearest to value and greater by relative rank with a count limit. +/// +/// Examples for map [{4=2},{9=10},{5=15},{0=17}]: +/// +/// * (value,rank,count) = [removed items] +/// * (11,1,1) = [{0=17}] +/// * (11,-1,1) = [{9=10}] +pub fn remove_by_value_relative_rank_range_count( + value: FilterExpression, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByValueRelRankRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map item identified by index. +pub fn remove_by_index( + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByIndex as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items starting at specified index to the end of map. +pub fn remove_by_index_range( + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByIndexRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes "count" map items starting at specified index. +pub fn remove_by_index_range_count( + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByIndexRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map item identified by rank. +pub fn remove_by_rank( + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByRank as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes map items starting at specified rank to the last ranked item. +pub fn remove_by_rank_range( + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByRankRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that removes "count" map items starting at specified rank. +pub fn remove_by_rank_range_count( + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::RemoveByRankRange as u8)), + ExpressionArgument::Value(Value::from(MapReturnType::None as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_write(bin, ctx, args) +} + +/// Create expression that returns list size. +/// +/// ``` +/// // Map bin "a" size > 7 +/// use aerospike::expressions::{gt, map_bin, int_val}; +/// use aerospike::expressions::maps::size; +/// +/// gt(size(map_bin("a".to_string()), &[]), int_val(7)); +/// +/// ``` +pub fn size(bin: FilterExpression, ctx: &[CdtContext]) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::Size as u8)), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, ExpType::INT, args) +} + +/// Create expression that selects map item identified by key and returns selected data +/// specified by returnType. +/// +/// ``` +/// // Map bin "a" contains key "B" +/// use aerospike::expressions::{ExpType, gt, string_val, map_bin, int_val}; +/// use aerospike::MapReturnType; +/// use aerospike::expressions::maps::get_by_key; +/// +/// gt(get_by_key(MapReturnType::Count, ExpType::INT, string_val("B".to_string()), map_bin("a".to_string()), &[]), int_val(0)); +/// ``` +/// +pub fn get_by_key( + return_type: MapReturnType, + value_type: ExpType, + key: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByKey as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, value_type, args) +} + +/// Create expression that selects map items identified by key range (keyBegin inclusive, keyEnd exclusive). +/// If keyBegin is null, the range is less than keyEnd. +/// If keyEnd is null, the range is greater than equal to keyBegin. +/// Expression returns selected data specified by returnType. +pub fn get_by_key_range( + return_type: MapReturnType, + key_begin: Option, + key_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByKeyInterval as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ]; + if let Some(val_beg) = key_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = key_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items identified by keys and returns selected data specified by returnType +pub fn get_by_key_list( + return_type: MapReturnType, + keys: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByKeyList as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(keys), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items nearest to key and greater by index. +/// Expression returns selected data specified by returnType. +/// +/// Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]: +/// +/// * (value,index) = [selected items] +/// * (5,0) = [{5=15},{9=10}] +/// * (5,1) = [{9=10}] +/// * (5,-1) = [{4=2},{5=15},{9=10}] +/// * (3,2) = [{9=10}] +/// * (3,-2) = [{0=17},{4=2},{5=15},{9=10}] +pub fn get_by_key_relative_index_range( + return_type: MapReturnType, + key: FilterExpression, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByKeyRelIndexRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items nearest to key and greater by index with a count limit. +/// Expression returns selected data specified by returnType. +/// +/// Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]: +/// +/// * (value,index,count) = [selected items] +/// * (5,0,1) = [{5=15}] +/// * (5,1,2) = [{9=10}] +/// * (5,-1,1) = [{4=2}] +/// * (3,2,1) = [{9=10}] +/// * (3,-2,2) = [{0=17}] +pub fn get_by_key_relative_index_range_count( + return_type: MapReturnType, + key: FilterExpression, + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByKeyRelIndexRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(key), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items identified by value and returns selected data +/// specified by returnType. +/// +/// ``` +/// // Map bin "a" contains value "BBB" +/// use aerospike::expressions::{gt, string_val, map_bin, int_val}; +/// use aerospike::MapReturnType; +/// use aerospike::expressions::maps::get_by_value; +/// +/// gt(get_by_value(MapReturnType::Count, string_val("BBB".to_string()), map_bin("a".to_string()), &[]), int_val(0)); +/// ``` +pub fn get_by_value( + return_type: MapReturnType, + value: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByValue as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items identified by value range (valueBegin inclusive, valueEnd exclusive) +/// If valueBegin is null, the range is less than valueEnd. +/// If valueEnd is null, the range is greater than equal to valueBegin. +/// +/// Expression returns selected data specified by returnType. +pub fn get_by_value_range( + return_type: MapReturnType, + value_begin: Option, + value_end: Option, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let mut args = vec![ + ExpressionArgument::Context(ctx.to_vec()), + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByValueInterval as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ]; + if let Some(val_beg) = value_begin { + args.push(ExpressionArgument::FilterExpression(val_beg)); + } else { + args.push(ExpressionArgument::FilterExpression(nil())); + } + if let Some(val_end) = value_end { + args.push(ExpressionArgument::FilterExpression(val_end)); + } + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items identified by values and returns selected data specified by returnType. +pub fn get_by_value_list( + return_type: MapReturnType, + values: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByValueList as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(values), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items nearest to value and greater by relative rank. +/// Expression returns selected data specified by returnType. +/// +/// Examples for map [{4=2},{9=10},{5=15},{0=17}]: +/// +/// * (value,rank) = [selected items] +/// * (11,1) = [{0=17}] +/// * (11,-1) = [{9=10},{5=15},{0=17}] +pub fn get_by_value_relative_rank_range( + return_type: MapReturnType, + value: FilterExpression, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByValueRelRankRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map items nearest to value and greater by relative rank with a count limit. +/// Expression returns selected data specified by returnType. +/// +/// Examples for map [{4=2},{9=10},{5=15},{0=17}]: +/// +/// * (value,rank,count) = [selected items] +/// * (11,1,1) = [{0=17}] +/// * (11,-1,1) = [{9=10}] +pub fn get_by_value_relative_rank_range_count( + return_type: MapReturnType, + value: FilterExpression, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByValueRelRankRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(value), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map item identified by index and returns selected data specified by returnType. +pub fn get_by_index( + return_type: MapReturnType, + value_type: ExpType, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByIndex as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, value_type, args) +} + +/// Create expression that selects map items starting at specified index to the end of map and returns selected +/// data specified by returnType. +pub fn get_by_index_range( + return_type: MapReturnType, + index: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByIndexRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects "count" map items starting at specified index and returns selected data +/// specified by returnType. +pub fn get_by_index_range_count( + return_type: MapReturnType, + index: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByIndexRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(index), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects map item identified by rank and returns selected data specified by returnType. +pub fn get_by_rank( + return_type: MapReturnType, + value_type: ExpType, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByRank as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, value_type, args) +} + +/// Create expression that selects map items starting at specified rank to the last ranked item and +/// returns selected data specified by returnType. +pub fn get_by_rank_range( + return_type: MapReturnType, + rank: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByRankRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +/// Create expression that selects "count" map items starting at specified rank and returns selected +/// data specified by returnType. +pub fn get_by_rank_range_count( + return_type: MapReturnType, + rank: FilterExpression, + count: FilterExpression, + bin: FilterExpression, + ctx: &[CdtContext], +) -> FilterExpression { + let args = vec![ + ExpressionArgument::Value(Value::from(CdtMapOpType::GetByRankRange as u8)), + ExpressionArgument::Value(Value::from(return_type as u8)), + ExpressionArgument::FilterExpression(rank), + ExpressionArgument::FilterExpression(count), + ExpressionArgument::Context(ctx.to_vec()), + ]; + add_read(bin, get_value_type(return_type), args) +} + +#[doc(hidden)] +fn add_read( + bin: FilterExpression, + return_type: ExpType, + arguments: Vec, +) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn add_write( + bin: FilterExpression, + ctx: &[CdtContext], + arguments: Vec, +) -> FilterExpression { + let return_type: ExpType; + if ctx.is_empty() { + return_type = ExpType::MAP + } else if (ctx[0].id & CtxType::ListIndex as u8) == 0 { + return_type = ExpType::MAP; + } else { + return_type = ExpType::LIST; + } + + FilterExpression { + cmd: Some(ExpOp::Call), + val: None, + bin: Some(Box::new(bin)), + flags: Some(MODULE | MODIFY), + module: Some(return_type), + exps: None, + arguments: Some(arguments), + } +} + +#[doc(hidden)] +fn get_value_type(return_type: MapReturnType) -> ExpType { + let t = return_type as u8 & !(MapReturnType::Inverted as u8); + if t == MapReturnType::Key as u8 || t == MapReturnType::Value as u8 { + ExpType::LIST + } else if t == MapReturnType::KeyValue as u8 { + ExpType::MAP + } else { + ExpType::INT + } +} diff --git a/src/expressions/mod.rs b/src/expressions/mod.rs new file mode 100644 index 00000000..d86848e7 --- /dev/null +++ b/src/expressions/mod.rs @@ -0,0 +1,841 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! Functions used for Filter Expressions. This module requires Aerospike Server version >= 5.2 + +pub mod bitwise; +pub mod hll; +pub mod lists; +pub mod maps; +pub mod regex_flag; + +use crate::commands::buffer::Buffer; +use crate::errors::Result; +use crate::msgpack::encoder::{pack_array_begin, pack_integer, pack_raw_string, pack_value}; +use crate::operations::cdt_context::CdtContext; +use crate::{ParticleType, Value}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// Expression Data Types for usage in some `FilterExpressions` on for example Map and List +#[derive(Debug, Clone, Copy)] +pub enum ExpType { + /// NIL Expression Type + NIL = 0, + /// BOOLEAN Expression Type + BOOL = 1, + /// INTEGER Expression Type + INT = 2, + /// STRING Expression Type + STRING = 3, + /// LIST Expression Type + LIST = 4, + /// MAP Expression Type + MAP = 5, + /// BLOB Expression Type + BLOB = 6, + /// FLOAT Expression Type + FLOAT = 7, + /// GEO String Expression Type + GEO = 8, + /// HLL Expression Type + HLL = 9, +} + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub enum ExpOp { + EQ = 1, + NE = 2, + GT = 3, + GE = 4, + LT = 5, + LE = 6, + Regex = 7, + Geo = 8, + And = 16, + Or = 17, + Not = 18, + DigestModulo = 64, + DeviceSize = 65, + LastUpdate = 66, + SinceUpdate = 67, + VoidTime = 68, + TTL = 69, + SetName = 70, + KeyExists = 71, + IsTombstone = 72, + Key = 80, + Bin = 81, + BinType = 82, + Quoted = 126, + Call = 127, +} + +#[doc(hidden)] +pub const MODIFY: i64 = 0x40; + +#[derive(Debug, Clone)] +#[doc(hidden)] +pub enum ExpressionArgument { + Value(Value), + FilterExpression(FilterExpression), + Context(Vec), +} + +/// Filter expression, which can be applied to most commands, to control which records are +/// affected by the command. Filter expression are created using the functions in the +/// [expressions](crate::expressions) module and its submodules. +#[derive(Debug, Clone)] +pub struct FilterExpression { + /// The Operation code + cmd: Option, + /// The Primary Value of the Operation + val: Option, + /// The Bin to use it on (REGEX for example) + bin: Option>, + /// The additional flags for the Operation (REGEX or return_type of Module for example) + flags: Option, + /// The optional Module flag for Module operations or Bin Types + module: Option, + /// Sub commands for the CmdExp operation + exps: Option>, + + arguments: Option>, +} + +#[doc(hidden)] +impl FilterExpression { + fn new( + cmd: Option, + val: Option, + bin: Option, + flags: Option, + module: Option, + exps: Option>, + ) -> FilterExpression { + if let Some(bin) = bin { + FilterExpression { + cmd, + val, + bin: Some(Box::new(bin)), + flags, + module, + exps, + arguments: None, + } + } else { + FilterExpression { + cmd, + val, + bin: None, + flags, + module, + exps, + arguments: None, + } + } + } + + fn pack_expression( + &self, + exps: &[FilterExpression], + buf: &mut Option<&mut Buffer>, + ) -> Result { + let mut size = 0; + size += pack_array_begin(buf, exps.len() + 1)?; + size += pack_integer(buf, self.cmd.unwrap() as i64)?; + for exp in exps { + size += exp.pack(buf)?; + } + Ok(size) + } + + fn pack_command(&self, cmd: ExpOp, buf: &mut Option<&mut Buffer>) -> Result { + let mut size = 0; + + match cmd { + ExpOp::Regex => { + size += pack_array_begin(buf, 4)?; + // The Operation + size += pack_integer(buf, cmd as i64)?; + // Regex Flags + size += pack_integer(buf, self.flags.unwrap())?; + // Raw String is needed instead of the msgpack String that the pack_value method would use. + size += pack_raw_string(buf, &self.val.clone().unwrap().to_string())?; + // The Bin + size += self.bin.clone().unwrap().pack(buf)?; + } + ExpOp::Call => { + // Packing logic for Module + size += pack_array_begin(buf, 5)?; + // The Operation + size += pack_integer(buf, cmd as i64)?; + // The Module Operation + size += pack_integer(buf, self.module.unwrap() as i64)?; + // The Module (List/Map or Bitwise) + size += pack_integer(buf, self.flags.unwrap())?; + // Encoding the Arguments + if let Some(args) = &self.arguments { + let mut len = 0; + for arg in args { + // First match to estimate the Size and write the Context + match arg { + ExpressionArgument::Value(_) + | ExpressionArgument::FilterExpression(_) => len += 1, + ExpressionArgument::Context(ctx) => { + if !ctx.is_empty() { + pack_array_begin(buf, 3)?; + pack_integer(buf, 0xff)?; + pack_array_begin(buf, ctx.len() * 2)?; + + for c in ctx { + pack_integer(buf, i64::from(c.id))?; + pack_value(buf, &c.value)?; + } + } + } + } + } + size += pack_array_begin(buf, len)?; + // Second match to write the real values + for arg in args { + match arg { + ExpressionArgument::Value(val) => { + size += pack_value(buf, val)?; + } + ExpressionArgument::FilterExpression(cmd) => { + size += cmd.pack(buf)?; + } + _ => {} + } + } + } else { + // No Arguments + size += pack_value(buf, &self.val.clone().unwrap())?; + } + // Write the Bin + size += self.bin.clone().unwrap().pack(buf)?; + } + ExpOp::Bin => { + // Bin Encoder + size += pack_array_begin(buf, 3)?; + // The Bin Operation + size += pack_integer(buf, cmd as i64)?; + // The Bin Type (INT/String etc.) + size += pack_integer(buf, self.module.unwrap() as i64)?; + // The name - Raw String is needed instead of the msgpack String that the pack_value method would use. + size += pack_raw_string(buf, &self.val.clone().unwrap().to_string())?; + } + ExpOp::BinType => { + // BinType encoder + size += pack_array_begin(buf, 2)?; + // BinType Operation + size += pack_integer(buf, cmd as i64)?; + // The name - Raw String is needed instead of the msgpack String that the pack_value method would use. + size += pack_raw_string(buf, &self.val.clone().unwrap().to_string())?; + } + _ => { + // Packing logic for all other Ops + if let Some(value) = &self.val { + // Operation has a Value + size += pack_array_begin(buf, 2)?; + // Write the Operation + size += pack_integer(buf, cmd as i64)?; + // Write the Value + size += pack_value(buf, value)?; + } else { + // Operation has no Value + size += pack_array_begin(buf, 1)?; + // Write the Operation + size += pack_integer(buf, cmd as i64)?; + } + } + } + + Ok(size) + } + + fn pack_value(&self, buf: &mut Option<&mut Buffer>) -> Result { + // Packing logic for Value based Ops + pack_value(buf, &self.val.clone().unwrap()) + } + + pub fn pack(&self, buf: &mut Option<&mut Buffer>) -> Result { + let mut size = 0; + if let Some(exps) = &self.exps { + size += self.pack_expression(exps, buf)?; + } else if let Some(cmd) = self.cmd { + size += self.pack_command(cmd, buf)?; + } else { + size += self.pack_value(buf)?; + } + + Ok(size) + } +} + +/// Create a record key expression of specified type. +/// ``` +/// use aerospike::expressions::{ExpType, ge, int_val, key}; +/// // Integer record key >= 100000 +/// ge(key(ExpType::INT), int_val(10000)); +/// ``` +pub fn key(exp_type: ExpType) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Key), + Some(Value::from(exp_type as i64)), + None, + None, + None, + None, + ) +} + +/// Create function that returns if the primary key is stored in the record meta data +/// as a boolean expression. This would occur when `send_key` is true on record write. +/// ``` +/// // Key exists in record meta data +/// use aerospike::expressions::key_exists; +/// key_exists(); +/// ``` +pub fn key_exists() -> FilterExpression { + FilterExpression::new(Some(ExpOp::KeyExists), None, None, None, None, None) +} + +/// Create 64 bit int bin expression. +/// ``` +/// // Integer bin "a" == 500 +/// use aerospike::expressions::{int_bin, int_val, eq}; +/// eq(int_bin("a".to_string()), int_val(500)); +/// ``` +pub fn int_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::INT), + None, + ) +} + +/// Create string bin expression. +/// ``` +/// // String bin "a" == "views" +/// use aerospike::expressions::{eq, string_bin, string_val}; +/// eq(string_bin("a".to_string()), string_val("views".to_string())); +/// ``` +pub fn string_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::STRING), + None, + ) +} + +/// Create blob bin expression. +/// ``` +/// // String bin "a" == [1,2,3] +/// use aerospike::expressions::{eq, blob_bin, blob_val}; +/// let blob: Vec = vec![1,2,3]; +/// eq(blob_bin("a".to_string()), blob_val(blob)); +/// ``` +pub fn blob_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::BLOB), + None, + ) +} + +/// Create 64 bit float bin expression. +/// ``` +/// use aerospike::expressions::{float_val, float_bin, eq}; +/// // Integer bin "a" == 500.5 +/// eq(float_bin("a".to_string()), float_val(500.5)); +/// ``` +pub fn float_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::FLOAT), + None, + ) +} + +/// Create geo bin expression. +/// ``` +/// // String bin "a" == region +/// use aerospike::expressions::{eq, geo_bin, string_val}; +/// let region = "{ \"type\": \"AeroCircle\", \"coordinates\": [[-122.0, 37.5], 50000.0] }"; +/// eq(geo_bin("a".to_string()), string_val(region.to_string())); +/// ``` +pub fn geo_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::GEO), + None, + ) +} + +/// Create list bin expression. +/// ``` +/// use aerospike::expressions::{ExpType, eq, int_val, list_bin}; +/// use aerospike::operations::lists::ListReturnType; +/// use aerospike::expressions::lists::get_by_index; +/// // String bin a[2] == 3 +/// eq(get_by_index(ListReturnType::Values, ExpType::INT, int_val(2), list_bin("a".to_string()), &[]), int_val(3)); +/// ``` +pub fn list_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::LIST), + None, + ) +} + +/// Create map bin expression. +/// +/// ``` +/// // Bin a["key"] == "value" +/// use aerospike::expressions::{ExpType, string_val, map_bin, eq}; +/// use aerospike::MapReturnType; +/// use aerospike::expressions::maps::get_by_key; +/// +/// eq( +/// get_by_key(MapReturnType::Value, ExpType::STRING, string_val("key".to_string()), map_bin("a".to_string()), &[]), +/// string_val("value".to_string())); +/// ``` +pub fn map_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::MAP), + None, + ) +} + +/// Create a HLL bin expression +/// +/// ``` +/// use aerospike::expressions::{gt, list_val, hll_bin, int_val}; +/// use aerospike::operations::hll::HLLPolicy; +/// use aerospike::Value; +/// use aerospike::expressions::hll::add; +/// +/// // Add values to HLL bin "a" and check count > 7 +/// let list = vec![Value::from(1)]; +/// gt(add(HLLPolicy::default(), list_val(list), hll_bin("a".to_string())), int_val(7)); +/// ``` +pub fn hll_bin(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Bin), + Some(Value::from(name)), + None, + None, + Some(ExpType::HLL), + None, + ) +} + +/// Create function that returns if bin of specified name exists. +/// ``` +/// // Bin "a" exists in record +/// use aerospike::expressions::bin_exists; +/// bin_exists("a".to_string()); +/// ``` +pub fn bin_exists(name: String) -> FilterExpression { + ne(bin_type(name), int_val(ParticleType::NULL as i64)) +} + +/// Create function that returns bin's integer particle type. +/// ``` +/// use aerospike::ParticleType; +/// use aerospike::expressions::{eq, bin_type, int_val}; +/// // Bin "a" particle type is a list +/// eq(bin_type("a".to_string()), int_val(ParticleType::LIST as i64)); +/// ``` +pub fn bin_type(name: String) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::BinType), + Some(Value::from(name)), + None, + None, + None, + None, + ) +} + +/// Create function that returns record set name string. +/// ``` +/// use aerospike::expressions::{eq, set_name, string_val}; +/// // Record set name == "myset +/// eq(set_name(), string_val("myset".to_string())); +/// ``` +pub fn set_name() -> FilterExpression { + FilterExpression::new(Some(ExpOp::SetName), None, None, None, None, None) +} + +/// Create function that returns record size on disk. +/// If server storage-engine is memory, then zero is returned. +/// ``` +/// use aerospike::expressions::{ge, device_size, int_val}; +/// // Record device size >= 100 KB +/// ge(device_size(), int_val(100*1024)); +/// ``` +pub fn device_size() -> FilterExpression { + FilterExpression::new(Some(ExpOp::DeviceSize), None, None, None, None, None) +} + +/// Create function that returns record last update time expressed as 64 bit integer +/// nanoseconds since 1970-01-01 epoch. +/// ``` +/// // Record last update time >=2020-08-01 +/// use aerospike::expressions::{ge, last_update, float_val}; +/// ge(last_update(), float_val(1.5962E+18)); +/// ``` +pub fn last_update() -> FilterExpression { + FilterExpression::new(Some(ExpOp::LastUpdate), None, None, None, None, None) +} + +/// Create expression that returns milliseconds since the record was last updated. +/// This expression usually evaluates quickly because record meta data is cached in memory. +/// +/// ``` +/// // Record last updated more than 2 hours ago +/// use aerospike::expressions::{gt, int_val, since_update}; +/// gt(since_update(), int_val(2 * 60 * 60 * 1000)); +/// ``` +pub fn since_update() -> FilterExpression { + FilterExpression::new(Some(ExpOp::SinceUpdate), None, None, None, None, None) +} + +/// Create function that returns record expiration time expressed as 64 bit integer +/// nanoseconds since 1970-01-01 epoch. +/// ``` +/// // Expires on 2020-08-01 +/// use aerospike::expressions::{and, ge, last_update, float_val, lt}; +/// and(vec![ge(last_update(), float_val(1.5962E+18)), lt(last_update(), float_val(1.5963E+18))]); +/// ``` +pub fn void_time() -> FilterExpression { + FilterExpression::new(Some(ExpOp::VoidTime), None, None, None, None, None) +} + +/// Create function that returns record expiration time (time to live) in integer seconds. +/// ``` +/// // Record expires in less than 1 hour +/// use aerospike::expressions::{lt, ttl, int_val}; +/// lt(ttl(), int_val(60*60)); +/// ``` +pub fn ttl() -> FilterExpression { + FilterExpression::new(Some(ExpOp::TTL), None, None, None, None, None) +} + +/// Create expression that returns if record has been deleted and is still in tombstone state. +/// This expression usually evaluates quickly because record meta data is cached in memory. +/// +/// ``` +/// // Deleted records that are in tombstone state. +/// use aerospike::expressions::{is_tombstone}; +/// is_tombstone(); +/// ``` +pub fn is_tombstone() -> FilterExpression { + FilterExpression::new(Some(ExpOp::IsTombstone), None, None, None, None, None) +} +/// Create function that returns record digest modulo as integer. +/// ``` +/// // Records that have digest(key) % 3 == 1 +/// use aerospike::expressions::{int_val, eq, digest_modulo}; +/// eq(digest_modulo(3), int_val(1)); +/// ``` +pub fn digest_modulo(modulo: i64) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::DigestModulo), + Some(Value::from(modulo)), + None, + None, + None, + None, + ) +} + +/// Create function like regular expression string operation. +/// ``` +/// use aerospike::RegexFlag; +/// use aerospike::expressions::{regex_compare, string_bin}; +/// // Select string bin "a" that starts with "prefix" and ends with "suffix". +/// // Ignore case and do not match newline. +/// regex_compare("prefix.*suffix".to_string(), RegexFlag::ICASE as i64 | RegexFlag::NEWLINE as i64, string_bin("a".to_string())); +/// ``` +pub fn regex_compare(regex: String, flags: i64, bin: FilterExpression) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Regex), + Some(Value::from(regex)), + Some(bin), + Some(flags), + None, + None, + ) +} + +/// Create compare geospatial operation. +/// ``` +/// use aerospike::expressions::{geo_compare, geo_bin, geo_val}; +/// // Query region within coordinates. +/// let region = "{\"type\": \"Polygon\", \"coordinates\": [ [[-122.500000, 37.000000],[-121.000000, 37.000000], [-121.000000, 38.080000],[-122.500000, 38.080000], [-122.500000, 37.000000]] ] }"; +/// geo_compare(geo_bin("a".to_string()), geo_val(region.to_string())); +/// ``` +pub fn geo_compare(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Geo), + None, + None, + None, + None, + Some(vec![left, right]), + ) +} + +/// Creates 64 bit integer value +pub fn int_val(val: i64) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Creates a Boolean value +pub fn bool_val(val: bool) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Creates String bin value +pub fn string_val(val: String) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Creates 64 bit float bin value +pub fn float_val(val: f64) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Creates Blob bin value +pub fn blob_val(val: Vec) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Create List bin Value +pub fn list_val(val: Vec) -> FilterExpression { + FilterExpression::new( + Some(ExpOp::Quoted), + Some(Value::from(val)), + None, + None, + None, + None, + ) +} + +/// Create Map bin Value +pub fn map_val(val: HashMap) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Create geospatial json string value. +pub fn geo_val(val: String) -> FilterExpression { + FilterExpression::new(None, Some(Value::from(val)), None, None, None, None) +} + +/// Create a Nil Value +pub fn nil() -> FilterExpression { + FilterExpression::new(None, Some(Value::Nil), None, None, None, None) +} +/// Create "not" operator expression. +/// ``` +/// // ! (a == 0 || a == 10) +/// use aerospike::expressions::{not, or, eq, int_bin, int_val}; +/// not(or(vec![eq(int_bin("a".to_string()), int_val(0)), eq(int_bin("a".to_string()), int_val(10))])); +/// ``` +pub fn not(exp: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Not), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![exp]), + arguments: None, + } +} + +/// Create "and" (&&) operator that applies to a variable number of expressions. +/// ``` +/// // (a > 5 || a == 0) && b < 3 +/// use aerospike::expressions::{and, or, gt, int_bin, int_val, eq, lt}; +/// and(vec![or(vec![gt(int_bin("a".to_string()), int_val(5)), eq(int_bin("a".to_string()), int_val(0))]), lt(int_bin("b".to_string()), int_val(3))]); +/// ``` +pub fn and(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::And), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create "or" (||) operator that applies to a variable number of expressions. +/// ``` +/// // a == 0 || b == 0 +/// use aerospike::expressions::{or, eq, int_bin, int_val}; +/// or(vec![eq(int_bin("a".to_string()), int_val(0)), eq(int_bin("b".to_string()), int_val(0))]); +/// ``` +pub fn or(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Or), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create equal (==) expression. +/// ``` +/// // a == 11 +/// use aerospike::expressions::{eq, int_bin, int_val}; +/// eq(int_bin("a".to_string()), int_val(11)); +/// ``` +pub fn eq(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::EQ), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +/// Create not equal (!=) expression +/// ``` +/// // a != 13 +/// use aerospike::expressions::{ne, int_bin, int_val}; +/// ne(int_bin("a".to_string()), int_val(13)); +/// ``` +pub fn ne(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::NE), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +/// Create greater than (>) operation. +/// ``` +/// // a > 8 +/// use aerospike::expressions::{gt, int_bin, int_val}; +/// gt(int_bin("a".to_string()), int_val(8)); +/// ``` +pub fn gt(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::GT), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +/// Create greater than or equal (>=) operation. +/// ``` +/// use aerospike::expressions::{ge, int_bin, int_val}; +/// // a >= 88 +/// ge(int_bin("a".to_string()), int_val(88)); +/// ``` +pub fn ge(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::GE), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +/// Create less than (<) operation. +/// ``` +/// // a < 1000 +/// use aerospike::expressions::{lt, int_bin, int_val}; +/// lt(int_bin("a".to_string()), int_val(1000)); +/// ``` +pub fn lt(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::LT), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +/// Create less than or equals (<=) operation. +/// ``` +/// use aerospike::expressions::{le, int_bin, int_val}; +/// // a <= 1 +/// le(int_bin("a".to_string()), int_val(1)); +/// ``` +pub fn le(left: FilterExpression, right: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::LE), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![left, right]), + arguments: None, + } +} + +// ---------------------------------------------- diff --git a/src/expressions/regex_flag.rs b/src/expressions/regex_flag.rs new file mode 100644 index 00000000..1b9e19ba --- /dev/null +++ b/src/expressions/regex_flag.rs @@ -0,0 +1,14 @@ +//! Regex Bit Flags +/// Used to change the Regex Mode in Filters +pub enum RegexFlag { + /// Use regex defaults. + NONE = 0, + /// Use POSIX Extended Regular Expression syntax when interpreting regex. + EXTENDED = 1, + /// Do not differentiate case. + ICASE = 2, + /// Do not report position of matches. + NOSUB = 3, + /// Match-any-character operators don't match a newline. + NEWLINE = 8, +} diff --git a/src/lib.rs b/src/lib.rs index 570d7fa8..b63f0f9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,7 +153,9 @@ extern crate scoped_pool; pub use batch::BatchRead; pub use bin::{Bin, Bins}; pub use client::Client; +pub use commands::particle_type::ParticleType; pub use errors::{Error, ErrorKind, Result}; +pub use expressions::regex_flag::RegexFlag; pub use key::Key; pub use net::Host; pub use operations::{MapPolicy, MapReturnType, MapWriteMode}; @@ -181,6 +183,7 @@ mod batch; mod client; mod cluster; mod commands; +pub mod expressions; mod msgpack; mod net; pub mod operations; diff --git a/src/msgpack/encoder.rs b/src/msgpack/encoder.rs index ff5bb8f9..87e4ba15 100644 --- a/src/msgpack/encoder.rs +++ b/src/msgpack/encoder.rs @@ -24,6 +24,7 @@ use crate::operations::cdt::{CdtArgument, CdtOperation}; use crate::operations::cdt_context::CdtContext; use crate::value::{FloatValue, Value}; +#[doc(hidden)] pub fn pack_value(buf: &mut Option<&mut Buffer>, val: &Value) -> Result { match *val { Value::Nil => pack_nil(buf), @@ -35,7 +36,7 @@ pub fn pack_value(buf: &mut Option<&mut Buffer>, val: &Value) -> Result { FloatValue::F64(_) => pack_f64(buf, f64::from(val)), FloatValue::F32(_) => pack_f32(buf, f32::from(val)), }, - Value::Blob(ref val) => pack_blob(buf, val), + Value::Blob(ref val) | Value::HLL(ref val) => pack_blob(buf, val), Value::List(ref val) => pack_array(buf, val), Value::HashMap(ref val) => pack_map(buf, val), Value::OrderedMap(_) => panic!("Ordered maps are not supported in this encoder."), @@ -43,6 +44,7 @@ pub fn pack_value(buf: &mut Option<&mut Buffer>, val: &Value) -> Result { } } +#[doc(hidden)] pub fn pack_empty_args_array(buf: &mut Option<&mut Buffer>) -> Result { let mut size = 0; size += pack_array_begin(buf, 0)?; @@ -50,6 +52,7 @@ pub fn pack_empty_args_array(buf: &mut Option<&mut Buffer>) -> Result { Ok(size) } +#[doc(hidden)] pub fn pack_cdt_op( buf: &mut Option<&mut Buffer>, cdt_op: &CdtOperation, @@ -67,10 +70,10 @@ pub fn pack_cdt_op( size += pack_array_begin(buf, ctx.len() * 2)?; for c in ctx { - if c.id != 0 { - size += pack_integer(buf, i64::from(c.id | c.flags))?; - } else { + if c.id == 0 { size += pack_integer(buf, i64::from(c.id))?; + } else { + size += pack_integer(buf, i64::from(c.id | c.flags))?; } size += pack_value(buf, &c.value)?; } @@ -95,6 +98,31 @@ pub fn pack_cdt_op( Ok(size) } +#[doc(hidden)] +pub fn pack_hll_op( + buf: &mut Option<&mut Buffer>, + hll_op: &CdtOperation, + _ctx: &[CdtContext], +) -> Result { + let mut size: usize = 0; + size += pack_array_begin(buf, hll_op.args.len() + 1)?; + size += pack_integer(buf, i64::from(hll_op.op))?; + if !hll_op.args.is_empty() { + for arg in &hll_op.args { + size += match *arg { + CdtArgument::Byte(byte) => pack_value(buf, &Value::from(byte))?, + CdtArgument::Int(int) => pack_value(buf, &Value::from(int))?, + CdtArgument::Value(value) => pack_value(buf, value)?, + CdtArgument::List(list) => pack_array(buf, list)?, + CdtArgument::Map(map) => pack_map(buf, map)?, + CdtArgument::Bool(bool_val) => pack_value(buf, &Value::from(bool_val))?, + } + } + } + Ok(size) +} + +#[doc(hidden)] pub fn pack_cdt_bit_op( buf: &mut Option<&mut Buffer>, cdt_op: &CdtOperation, @@ -107,10 +135,10 @@ pub fn pack_cdt_bit_op( size += pack_array_begin(buf, ctx.len() * 2)?; for c in ctx { - if c.id != 0 { - size += pack_integer(buf, i64::from(c.id | c.flags))?; - } else { + if c.id == 0 { size += pack_integer(buf, i64::from(c.id))?; + } else { + size += pack_integer(buf, i64::from(c.id | c.flags))?; } size += pack_value(buf, &c.value)?; } @@ -134,6 +162,7 @@ pub fn pack_cdt_bit_op( Ok(size) } +#[doc(hidden)] pub fn pack_array(buf: &mut Option<&mut Buffer>, values: &[Value]) -> Result { let mut size = 0; @@ -145,7 +174,8 @@ pub fn pack_array(buf: &mut Option<&mut Buffer>, values: &[Value]) -> Result, map: &HashMap) -> Result { +#[doc(hidden)] +pub fn pack_map(buf: &mut Option<&mut Buffer>, map: &HashMap) -> Result { let mut size = 0; size += pack_map_begin(buf, map.len())?; @@ -175,21 +205,24 @@ const MSGPACK_MARKER_NI64: u8 = 0xd3; // This method is not compatible with MsgPack specs and is only used by aerospike client<->server // for wire transfer only -fn pack_raw_u16(buf: &mut Option<&mut Buffer>, val: u16) -> Result { +#[doc(hidden)] +pub fn pack_raw_u16(buf: &mut Option<&mut Buffer>, val: u16) -> Result { if let Some(ref mut buf) = *buf { buf.write_u16(val)?; } Ok(2) } -fn pack_half_byte(buf: &mut Option<&mut Buffer>, val: u8) -> Result { +#[doc(hidden)] +pub fn pack_half_byte(buf: &mut Option<&mut Buffer>, val: u8) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(val)?; } Ok(1) } -fn pack_byte(buf: &mut Option<&mut Buffer>, marker: u8, val: u8) -> Result { +#[doc(hidden)] +pub fn pack_byte(buf: &mut Option<&mut Buffer>, marker: u8, val: u8) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(marker)?; buf.write_u8(val)?; @@ -197,14 +230,16 @@ fn pack_byte(buf: &mut Option<&mut Buffer>, marker: u8, val: u8) -> Result) -> Result { +#[doc(hidden)] +pub fn pack_nil(buf: &mut Option<&mut Buffer>) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(MSGPACK_MARKER_NIL)?; } Ok(1) } -fn pack_bool(buf: &mut Option<&mut Buffer>, val: bool) -> Result { +#[doc(hidden)] +pub fn pack_bool(buf: &mut Option<&mut Buffer>, val: bool) -> Result { if let Some(ref mut buf) = *buf { if val { buf.write_u8(MSGPACK_MARKER_BOOL_TRUE)?; @@ -215,7 +250,8 @@ fn pack_bool(buf: &mut Option<&mut Buffer>, val: bool) -> Result { Ok(1) } -fn pack_map_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result { +#[doc(hidden)] +pub fn pack_map_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result { match length { val if val < 16 => pack_half_byte(buf, 0x80 | (length as u8)), val if val >= 16 && val < 2 ^ 16 => pack_i16(buf, 0xde, length as i16), @@ -223,7 +259,8 @@ fn pack_map_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result } } -fn pack_array_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result { +#[doc(hidden)] +pub fn pack_array_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result { match length { val if val < 16 => pack_half_byte(buf, 0x90 | (length as u8)), val if val >= 16 && val < 2 ^ 16 => pack_i16(buf, 0xdc, length as i16), @@ -231,7 +268,8 @@ fn pack_array_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result, length: usize) -> Result { +#[doc(hidden)] +pub fn pack_byte_array_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result { match length { val if val < 32 => pack_half_byte(buf, 0xa0 | (length as u8)), val if val >= 32 && val < 2 ^ 16 => pack_i16(buf, 0xda, length as i16), @@ -239,7 +277,8 @@ fn pack_byte_array_begin(buf: &mut Option<&mut Buffer>, length: usize) -> Result } } -fn pack_blob(buf: &mut Option<&mut Buffer>, val: &[u8]) -> Result { +#[doc(hidden)] +pub fn pack_blob(buf: &mut Option<&mut Buffer>, val: &[u8]) -> Result { let mut size = val.len() + 1; size += pack_byte_array_begin(buf, size)?; @@ -251,7 +290,8 @@ fn pack_blob(buf: &mut Option<&mut Buffer>, val: &[u8]) -> Result { Ok(size) } -fn pack_string(buf: &mut Option<&mut Buffer>, val: &str) -> Result { +#[doc(hidden)] +pub fn pack_string(buf: &mut Option<&mut Buffer>, val: &str) -> Result { let mut size = val.len() + 1; size += pack_byte_array_begin(buf, size)?; @@ -263,6 +303,19 @@ fn pack_string(buf: &mut Option<&mut Buffer>, val: &str) -> Result { Ok(size) } +#[doc(hidden)] +pub fn pack_raw_string(buf: &mut Option<&mut Buffer>, val: &str) -> Result { + let mut size = val.len(); + + size += pack_byte_array_begin(buf, size)?; + if let Some(ref mut buf) = *buf { + buf.write_str(val)?; + } + + Ok(size) +} + +#[doc(hidden)] fn pack_geo_json(buf: &mut Option<&mut Buffer>, val: &str) -> Result { let mut size = val.len() + 1; @@ -275,7 +328,8 @@ fn pack_geo_json(buf: &mut Option<&mut Buffer>, val: &str) -> Result { Ok(size) } -fn pack_integer(buf: &mut Option<&mut Buffer>, val: i64) -> Result { +#[doc(hidden)] +pub fn pack_integer(buf: &mut Option<&mut Buffer>, val: i64) -> Result { match val { val if val >= 0 && val < 2 ^ 7 => pack_half_byte(buf, val as u8), val if val >= 2 ^ 7 && val < i64::from(i8::max_value()) => { @@ -307,7 +361,8 @@ fn pack_integer(buf: &mut Option<&mut Buffer>, val: i64) -> Result { } } -fn pack_i16(buf: &mut Option<&mut Buffer>, marker: u8, val: i16) -> Result { +#[doc(hidden)] +pub fn pack_i16(buf: &mut Option<&mut Buffer>, marker: u8, val: i16) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(marker)?; buf.write_i16(val)?; @@ -315,7 +370,8 @@ fn pack_i16(buf: &mut Option<&mut Buffer>, marker: u8, val: i16) -> Result, marker: u8, val: i32) -> Result { +#[doc(hidden)] +pub fn pack_i32(buf: &mut Option<&mut Buffer>, marker: u8, val: i32) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(marker)?; buf.write_i32(val)?; @@ -323,7 +379,8 @@ fn pack_i32(buf: &mut Option<&mut Buffer>, marker: u8, val: i32) -> Result, marker: u8, val: i64) -> Result { +#[doc(hidden)] +pub fn pack_i64(buf: &mut Option<&mut Buffer>, marker: u8, val: i64) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(marker)?; buf.write_i64(val)?; @@ -331,7 +388,8 @@ fn pack_i64(buf: &mut Option<&mut Buffer>, marker: u8, val: i64) -> Result, val: u64) -> Result { +#[doc(hidden)] +pub fn pack_u64(buf: &mut Option<&mut Buffer>, val: u64) -> Result { if val <= i64::max_value() as u64 { return pack_integer(buf, val as i64); } @@ -343,7 +401,8 @@ fn pack_u64(buf: &mut Option<&mut Buffer>, val: u64) -> Result { Ok(9) } -fn pack_f32(buf: &mut Option<&mut Buffer>, val: f32) -> Result { +#[doc(hidden)] +pub fn pack_f32(buf: &mut Option<&mut Buffer>, val: f32) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(0xca)?; buf.write_f32(val)?; @@ -351,7 +410,8 @@ fn pack_f32(buf: &mut Option<&mut Buffer>, val: f32) -> Result { Ok(5) } -fn pack_f64(buf: &mut Option<&mut Buffer>, val: f64) -> Result { +#[doc(hidden)] +pub fn pack_f64(buf: &mut Option<&mut Buffer>, val: f64) -> Result { if let Some(ref mut buf) = *buf { buf.write_u8(0xcb)?; buf.write_f64(val)?; diff --git a/src/operations/bitwise.rs b/src/operations/bitwise.rs index afcefe5d..11f96e08 100644 --- a/src/operations/bitwise.rs +++ b/src/operations/bitwise.rs @@ -124,11 +124,14 @@ impl Default for BitPolicy { /// Creates byte "resize" operation. /// Server resizes byte[] to byteSize according to resizeFlags. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010] /// byteSize = 4 /// resizeFlags = 0 /// bin result = [0b00000001, 0b01000010, 0b00000000, 0b00000000] +/// ``` pub fn resize<'a>( bin: &'a str, byte_size: i64, @@ -158,11 +161,14 @@ pub fn resize<'a>( /// Creates byte "insert" operation. /// Server inserts value bytes into byte[] bin at byteOffset. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// byteOffset = 1 /// value = [0b11111111, 0b11000111] /// bin result = [0b00000001, 0b11111111, 0b11000111, 0b01000010, 0b00000011, 0b00000100, 0b00000101] +/// ``` pub fn insert<'a>( bin: &'a str, byte_offset: i64, @@ -190,11 +196,14 @@ pub fn insert<'a>( /// Creates byte "remove" operation. /// Server removes bytes from byte[] bin at byteOffset for byteSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// byteOffset = 2 /// byteSize = 3 /// bin result = [0b00000001, 0b01000010] +/// ``` pub fn remove<'a>( bin: &'a str, byte_offset: i64, @@ -222,12 +231,15 @@ pub fn remove<'a>( /// Creates bit "set" operation. /// Server sets value on byte[] bin at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 13 /// bitSize = 3 /// value = [0b11100000] /// bin result = [0b00000001, 0b01000111, 0b00000011, 0b00000100, 0b00000101] +/// ``` pub fn set<'a>( bin: &'a str, bit_offset: i64, @@ -257,12 +269,15 @@ pub fn set<'a>( /// Creates bit "or" operation. /// Server performs bitwise "or" on value and byte[] bin at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 17 /// bitSize = 6 /// value = [0b10101000] /// bin result = [0b00000001, 0b01000010, 0b01010111, 0b00000100, 0b00000101] +/// ``` pub fn or<'a>( bin: &'a str, bit_offset: i64, @@ -292,12 +307,15 @@ pub fn or<'a>( /// Creates bit "exclusive or" operation. /// Server performs bitwise "xor" on value and byte[] bin at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 17 /// bitSize = 6 /// value = [0b10101100] /// bin result = [0b00000001, 0b01000010, 0b01010101, 0b00000100, 0b00000101] +/// ``` pub fn xor<'a>( bin: &'a str, bit_offset: i64, @@ -327,12 +345,15 @@ pub fn xor<'a>( /// Creates bit "and" operation. /// Server performs bitwise "and" on value and byte[] bin at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 23 /// bitSize = 9 /// value = [0b00111100, 0b10000000] /// bin result = [0b00000001, 0b01000010, 0b00000010, 0b00000000, 0b00000101] +/// ``` pub fn and<'a>( bin: &'a str, bit_offset: i64, @@ -362,11 +383,14 @@ pub fn and<'a>( /// Creates bit "not" operation. /// Server negates byte[] bin starting at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 25 /// bitSize = 6 /// bin result = [0b00000001, 0b01000010, 0b00000011, 0b01111010, 0b00000101] +/// ``` pub fn not<'a>( bin: &'a str, bit_offset: i64, @@ -394,12 +418,15 @@ pub fn not<'a>( /// Creates bit "left shift" operation. /// Server shifts left byte[] bin starting at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 32 /// bitSize = 8 /// shift = 3 /// bin result = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00101000] +/// ``` pub fn lshift<'a>( bin: &'a str, bit_offset: i64, @@ -429,12 +456,15 @@ pub fn lshift<'a>( /// Creates bit "right shift" operation. /// Server shifts right byte[] bin starting at bitOffset for bitSize. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 0 /// bitSize = 9 /// shift = 1 /// bin result = [0b00000000, 0b11000010, 0b00000011, 0b00000100, 0b00000101] +/// ``` pub fn rshift<'a>( bin: &'a str, bit_offset: i64, @@ -466,13 +496,16 @@ pub fn rshift<'a>( /// Signed indicates if bits should be treated as a signed number. /// If add overflows/underflows, `CdtBitwiseOverflowAction` is used. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 24 /// bitSize = 16 /// value = 128 /// signed = false /// bin result = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b10000101] +/// ``` pub fn add<'a>( bin: &'a str, bit_offset: i64, @@ -512,13 +545,16 @@ pub fn add<'a>( /// Signed indicates if bits should be treated as a signed number. /// If add overflows/underflows, `CdtBitwiseOverflowAction` is used. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 24 /// bitSize = 16 /// value = 128 /// signed = false /// bin result = [0b00000001, 0b01000010, 0b00000011, 0b0000011, 0b10000101] +/// ``` pub fn subtract<'a>( bin: &'a str, bit_offset: i64, @@ -556,12 +592,15 @@ pub fn subtract<'a>( /// Creates bit "setInt" operation. /// Server sets value to byte[] bin starting at bitOffset for bitSize. Size must be <= 64. /// Server does not return a value. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 1 /// bitSize = 8 /// value = 127 /// bin result = [0b00111111, 0b11000010, 0b00000011, 0b0000100, 0b00000101] +/// ``` pub fn set_int<'a>( bin: &'a str, bit_offset: i64, @@ -590,11 +629,14 @@ pub fn set_int<'a>( /// Creates bit "get" operation. /// Server returns bits from byte[] bin starting at bitOffset for bitSize. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 9 /// bitSize = 5 /// returns [0b1000000] +/// ``` pub fn get(bin: &str, bit_offset: i64, bit_size: i64) -> Operation { let cdt_op = CdtOperation { op: CdtBitwiseOpType::Get as u8, @@ -612,11 +654,14 @@ pub fn get(bin: &str, bit_offset: i64, bit_size: i64) -> Operation { /// Creates bit "count" operation. /// Server returns integer count of set bits from byte[] bin starting at bitOffset for bitSize. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 20 /// bitSize = 4 /// returns 2 +/// ``` pub fn count(bin: &str, bit_offset: i64, bit_size: i64) -> Operation { let cdt_op = CdtOperation { op: CdtBitwiseOpType::Count as u8, @@ -635,12 +680,15 @@ pub fn count(bin: &str, bit_offset: i64, bit_size: i64) -> Operation { /// Creates bit "left scan" operation. /// Server returns integer bit offset of the first specified value bit in byte[] bin /// starting at bitOffset for bitSize. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 24 /// bitSize = 8 /// value = true /// returns 5 +/// ``` pub fn lscan(bin: &str, bit_offset: i64, bit_size: i64, value: bool) -> Operation { let cdt_op = CdtOperation { op: CdtBitwiseOpType::LScan as u8, @@ -663,12 +711,15 @@ pub fn lscan(bin: &str, bit_offset: i64, bit_size: i64, value: bool) -> Operatio /// Creates bit "right scan" operation. /// Server returns integer bit offset of the last specified value bit in byte[] bin /// starting at bitOffset for bitSize. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 32 /// bitSize = 8 /// value = true /// returns 7 +/// ``` pub fn rscan(bin: &str, bit_offset: i64, bit_size: i64, value: bool) -> Operation { let cdt_op = CdtOperation { op: CdtBitwiseOpType::RScan as u8, @@ -691,12 +742,15 @@ pub fn rscan(bin: &str, bit_offset: i64, bit_size: i64, value: bool) -> Operatio /// Creates bit "get integer" operation. /// Server returns integer from byte[] bin starting at bitOffset for bitSize. /// Signed indicates if bits should be treated as a signed number. +/// /// Example: +/// ```text /// bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] /// bitOffset = 8 /// bitSize = 16 /// signed = false /// returns 16899 +/// ``` pub fn get_int(bin: &str, bit_offset: i64, bit_size: i64, signed: bool) -> Operation { let mut args = vec![CdtArgument::Int(bit_offset), CdtArgument::Int(bit_size)]; if signed { diff --git a/src/operations/cdt_context.rs b/src/operations/cdt_context.rs index 6eec0fbf..e7bfb592 100644 --- a/src/operations/cdt_context.rs +++ b/src/operations/cdt_context.rs @@ -21,11 +21,21 @@ use crate::Value; // Empty Context for scalar operations pub const DEFAULT_CTX: &[CdtContext] = &[]; +#[doc(hidden)] +pub enum CtxType { + ListIndex = 0x10, + ListRank = 0x11, + ListValue = 0x13, + MapIndex = 0x20, + MapRank = 0x21, + MapKey = 0x22, + MapValue = 0x23, +} /// `CdtContext` defines Nested CDT context. Identifies the location of nested list/map to apply the operation. /// for the current level. /// An array of CTX identifies location of the list/map on multiple /// levels on nesting. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CdtContext { /// Context Type pub id: u8, @@ -47,7 +57,7 @@ pub struct CdtContext { /// -3: Third to last item. pub const fn ctx_list_index(index: i64) -> CdtContext { CdtContext { - id: 0x10, + id: CtxType::ListIndex as u8, flags: 0, value: Value::Int(index), } @@ -56,7 +66,7 @@ pub const fn ctx_list_index(index: i64) -> CdtContext { /// list with given type at index offset, given an order and pad. pub fn ctx_list_index_create(index: i64, order: ListOrderType, pad: bool) -> CdtContext { CdtContext { - id: 0x10, + id: CtxType::ListIndex as u8, flags: list_order_flag(order, pad), value: Value::Int(index), } @@ -68,7 +78,7 @@ pub fn ctx_list_index_create(index: i64, order: ListOrderType, pad: bool) -> Cdt /// -1 = largest value pub const fn ctx_list_rank(rank: i64) -> CdtContext { CdtContext { - id: 0x11, + id: CtxType::ListRank as u8, flags: 0, value: Value::Int(rank), } @@ -77,7 +87,7 @@ pub const fn ctx_list_rank(rank: i64) -> CdtContext { /// Defines Lookup list by value. pub const fn ctx_list_value(key: Value) -> CdtContext { CdtContext { - id: 0x13, + id: CtxType::ListValue as u8, flags: 0, value: key, } @@ -92,7 +102,7 @@ pub const fn ctx_list_value(key: Value) -> CdtContext { /// -3: Third to last item. pub const fn ctx_map_index(key: Value) -> CdtContext { CdtContext { - id: 0x20, + id: CtxType::MapIndex as u8, flags: 0, value: key, } @@ -104,7 +114,7 @@ pub const fn ctx_map_index(key: Value) -> CdtContext { /// -1 = largest value pub const fn ctx_map_rank(rank: i64) -> CdtContext { CdtContext { - id: 0x21, + id: CtxType::MapRank as u8, flags: 0, value: Value::Int(rank), } @@ -113,7 +123,7 @@ pub const fn ctx_map_rank(rank: i64) -> CdtContext { /// Defines Lookup map by key. pub const fn ctx_map_key(key: Value) -> CdtContext { CdtContext { - id: 0x22, + id: CtxType::MapKey as u8, flags: 0, value: key, } @@ -122,7 +132,7 @@ pub const fn ctx_map_key(key: Value) -> CdtContext { /// Create map with given type at map key. pub const fn ctx_map_key_create(key: Value, order: MapOrder) -> CdtContext { CdtContext { - id: 0x22, + id: CtxType::MapKey as u8, flags: order as u8, value: key, } @@ -131,7 +141,7 @@ pub const fn ctx_map_key_create(key: Value, order: MapOrder) -> CdtContext { /// Defines Lookup map by value. pub const fn ctx_map_value(key: Value) -> CdtContext { CdtContext { - id: 0x23, + id: CtxType::MapValue as u8, flags: 0, value: key, } diff --git a/src/operations/hll.rs b/src/operations/hll.rs new file mode 100644 index 00000000..b2f856d1 --- /dev/null +++ b/src/operations/hll.rs @@ -0,0 +1,313 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +//! `HyperLogLog` operations on HLL items nested in lists/maps are not currently +//! supported by the server. + +use crate::msgpack::encoder::pack_hll_op; +use crate::operations::cdt::{CdtArgument, CdtOperation}; +use crate::operations::cdt_context::DEFAULT_CTX; +use crate::operations::{Operation, OperationBin, OperationData, OperationType}; +use crate::Value; + +/// `HLLWriteFlags` determines write flags for HLL +#[derive(Debug, Clone, Copy)] +pub enum HLLWriteFlags { + /// Default. Allow create or update. + Default = 0, + /// If the bin already exists, the operation will be denied. + /// If the bin does not exist, a new bin will be created. + CreateOnly = 1, + /// If the bin already exists, the bin will be overwritten. + /// If the bin does not exist, the operation will be denied. + UpdateOnly = 2, + /// Do not raise error if operation is denied. + NoFail = 4, + /// Allow the resulting set to be the minimum of provided index bits. + /// Also, allow the usage of less precise HLL algorithms when minHash bits + /// of all participating sets do not match. + AllowFold = 8, +} + +/// `HLLPolicy` operation policy. +#[derive(Debug, Clone, Copy)] +pub struct HLLPolicy { + /// CdtListWriteFlags + pub flags: HLLWriteFlags, +} + +impl HLLPolicy { + /// Use specified `HLLWriteFlags` when performing `HLL` operations + pub const fn new(write_flags: HLLWriteFlags) -> Self { + HLLPolicy { flags: write_flags } + } +} + +impl Default for HLLPolicy { + /// Returns the default policy for HLL operations. + fn default() -> Self { + HLLPolicy::new(HLLWriteFlags::Default) + } +} + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub enum HLLOpType { + Init = 0, + Add = 1, + SetUnion = 2, + SetCount = 3, + Fold = 4, + Count = 50, + Union = 51, + UnionCount = 52, + IntersectCount = 53, + Similarity = 54, + Describe = 55, +} + +/// Create HLL init operation. +/// Server creates a new HLL or resets an existing HLL. +/// Server does not return a value. +pub fn init<'a>(policy: &HLLPolicy, bin: &'a str, index_bit_count: i64) -> Operation<'a> { + init_with_min_hash(policy, bin, index_bit_count, -1) +} + +/// Create HLL init operation with minhash bits. +/// Server creates a new HLL or resets an existing HLL. +/// Server does not return a value. +pub fn init_with_min_hash<'a>( + policy: &HLLPolicy, + bin: &'a str, + index_bit_count: i64, + min_hash_bit_count: i64, +) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::Init as u8, + encoder: Box::new(pack_hll_op), + args: vec![ + CdtArgument::Int(index_bit_count), + CdtArgument::Int(min_hash_bit_count), + CdtArgument::Byte(policy.flags as u8), + ], + }; + Operation { + op: OperationType::HllWrite, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL add operation. This operation assumes HLL bin already exists. +/// Server adds values to the HLL set. +/// Server returns number of entries that caused HLL to update a register. +pub fn add<'a>(policy: &HLLPolicy, bin: &'a str, list: &'a [Value]) -> Operation<'a> { + add_with_index_and_min_hash(policy, bin, list, -1, -1) +} + +/// Create HLL add operation. +/// Server adds values to HLL set. If HLL bin does not exist, use `indexBitCount` to create HLL bin. +/// Server returns number of entries that caused HLL to update a register. +pub fn add_with_index<'a>( + policy: &HLLPolicy, + bin: &'a str, + list: &'a [Value], + index_bit_count: i64, +) -> Operation<'a> { + add_with_index_and_min_hash(policy, bin, list, index_bit_count, -1) +} + +/// Create HLL add operation with minhash bits. +/// Server adds values to HLL set. If HLL bin does not exist, use `indexBitCount` and `minHashBitCount` +/// to create HLL bin. Server returns number of entries that caused HLL to update a register. +pub fn add_with_index_and_min_hash<'a>( + policy: &HLLPolicy, + bin: &'a str, + list: &'a [Value], + index_bit_count: i64, + min_hash_bit_count: i64, +) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::Add as u8, + encoder: Box::new(pack_hll_op), + args: vec![ + CdtArgument::List(list), + CdtArgument::Int(index_bit_count), + CdtArgument::Int(min_hash_bit_count), + CdtArgument::Byte(policy.flags as u8), + ], + }; + Operation { + op: OperationType::HllWrite, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL set union operation. +/// Server sets union of specified HLL objects with HLL bin. +/// Server does not return a value. +pub fn set_union<'a>(policy: &HLLPolicy, bin: &'a str, list: &'a [Value]) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::SetUnion as u8, + encoder: Box::new(pack_hll_op), + args: vec![ + CdtArgument::List(list), + CdtArgument::Byte(policy.flags as u8), + ], + }; + Operation { + op: OperationType::HllWrite, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL refresh operation. +/// Server updates the cached count (if stale) and returns the count. +pub fn refresh_count(bin: &str) -> Operation { + let cdt_op = CdtOperation { + op: HLLOpType::SetCount as u8, + encoder: Box::new(pack_hll_op), + args: vec![], + }; + Operation { + op: OperationType::HllWrite, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL fold operation. +/// Servers folds `indexBitCount` to the specified value. +/// This can only be applied when `minHashBitCount` on the HLL bin is 0. +/// Server does not return a value. +pub fn fold(bin: &str, index_bit_count: i64) -> Operation { + let cdt_op = CdtOperation { + op: HLLOpType::Fold as u8, + encoder: Box::new(pack_hll_op), + args: vec![CdtArgument::Int(index_bit_count)], + }; + Operation { + op: OperationType::HllWrite, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL getCount operation. +/// Server returns estimated number of elements in the HLL bin. +pub fn get_count(bin: &str) -> Operation { + let cdt_op = CdtOperation { + op: HLLOpType::Count as u8, + encoder: Box::new(pack_hll_op), + args: vec![], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL getUnion operation. +/// Server returns an HLL object that is the union of all specified HLL objects in the list +/// with the HLL bin. +pub fn get_union<'a>(bin: &'a str, list: &'a [Value]) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::Union as u8, + encoder: Box::new(pack_hll_op), + args: vec![CdtArgument::List(list)], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL `get_union_count` operation. +/// Server returns estimated number of elements that would be contained by the union of these +/// HLL objects. +pub fn get_union_count<'a>(bin: &'a str, list: &'a [Value]) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::UnionCount as u8, + encoder: Box::new(pack_hll_op), + args: vec![CdtArgument::List(list)], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL `get_intersect_count` operation. +/// Server returns estimated number of elements that would be contained by the intersection of +/// these HLL objects. +pub fn get_intersect_count<'a>(bin: &'a str, list: &'a [Value]) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::IntersectCount as u8, + encoder: Box::new(pack_hll_op), + args: vec![CdtArgument::List(list)], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL getSimilarity operation. +/// Server returns estimated similarity of these HLL objects. Return type is a double. +pub fn get_similarity<'a>(bin: &'a str, list: &'a [Value]) -> Operation<'a> { + let cdt_op = CdtOperation { + op: HLLOpType::Similarity as u8, + encoder: Box::new(pack_hll_op), + args: vec![CdtArgument::List(list)], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} + +/// Create HLL describe operation. +/// Server returns `indexBitCount` and `minHashBitCount` used to create HLL bin in a list of longs. +/// The list size is 2. +pub fn describe(bin: &str) -> Operation { + let cdt_op = CdtOperation { + op: HLLOpType::Describe as u8, + encoder: Box::new(pack_hll_op), + args: vec![], + }; + Operation { + op: OperationType::HllRead, + ctx: DEFAULT_CTX, + bin: OperationBin::Name(bin), + data: OperationData::HLLOp(cdt_op), + } +} diff --git a/src/operations/lists.rs b/src/operations/lists.rs index 2d2702c4..63cefe9d 100644 --- a/src/operations/lists.rs +++ b/src/operations/lists.rs @@ -492,8 +492,8 @@ pub fn remove_by_value_range<'a>( /// Server removes list items nearest to value and greater by relative rank. /// Server returns removed data specified by returnType. /// -/// Examples for ordered list [0,4,5,9,11,15]: -/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text /// (value,rank) = [removed items] /// (5,0) = [5,9,11,15] /// (5,1) = [9,11,15] @@ -501,6 +501,7 @@ pub fn remove_by_value_range<'a>( /// (3,0) = [4,5,9,11,15] /// (3,3) = [11,15] /// (3,-3) = [0,4,5,9,11,15] +/// ``` pub fn remove_by_value_relative_rank_range<'a>( bin: &'a str, return_type: ListReturnType, @@ -527,8 +528,9 @@ pub fn remove_by_value_relative_rank_range<'a>( /// Creates a list remove by value relative to rank range operation. /// Server removes list items nearest to value and greater by relative rank with a count limit. /// Server returns removed data specified by returnType. -/// Examples for ordered list [0,4,5,9,11,15]: /// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text /// (value,rank,count) = [removed items] /// (5,0,2) = [5,9] /// (5,1,1) = [9] @@ -536,6 +538,7 @@ pub fn remove_by_value_relative_rank_range<'a>( /// (3,0,1) = [4] /// (3,3,7) = [11,15] /// (3,-3,2) = [] +/// ``` pub fn remove_by_value_relative_rank_range_count<'a>( bin: &'a str, return_type: ListReturnType, @@ -1021,8 +1024,8 @@ pub fn get_by_rank_range_count( /// Server selects list items nearest to value and greater by relative rank. /// Server returns selected data specified by returnType. /// -/// Examples for ordered list [0,4,5,9,11,15]: -/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text /// (value,rank) = [selected items] /// (5,0) = [5,9,11,15] /// (5,1) = [9,11,15] @@ -1030,6 +1033,7 @@ pub fn get_by_rank_range_count( /// (3,0) = [4,5,9,11,15] /// (3,3) = [11,15] /// (3,-3) = [0,4,5,9,11,15] +/// ``` pub fn get_by_value_relative_rank_range<'a>( bin: &'a str, value: &'a Value, @@ -1057,8 +1061,8 @@ pub fn get_by_value_relative_rank_range<'a>( /// Server selects list items nearest to value and greater by relative rank with a count limit. /// Server returns selected data specified by returnType. /// -/// Examples for ordered list [0,4,5,9,11,15]: -/// +/// Examples for ordered list \[0, 4, 5, 9, 11, 15\]: +/// ```text /// (value,rank,count) = [selected items] /// (5,0,2) = [5,9] /// (5,1,1) = [9] @@ -1066,6 +1070,7 @@ pub fn get_by_value_relative_rank_range<'a>( /// (3,0,1) = [4] /// (3,3,7) = [11,15] /// (3,-3,2) = [] +/// ``` pub fn get_by_value_relative_rank_range_count<'a>( bin: &'a str, value: &'a Value, diff --git a/src/operations/maps.rs b/src/operations/maps.rs index b5311263..14c74dd8 100644 --- a/src/operations/maps.rs +++ b/src/operations/maps.rs @@ -100,7 +100,7 @@ pub enum MapOrder { } /// Map return type. Type of data to return when selecting or removing items from the map. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum MapReturnType { /// Do not return a result. None = 0, @@ -153,7 +153,7 @@ pub enum MapReturnType { } /// Unique key map write type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum MapWriteMode { /// If the key already exists, the item will be overwritten. /// If the key does not exist, a new item will be created. @@ -171,8 +171,10 @@ pub enum MapWriteMode { /// `MapPolicy` directives when creating a map and writing map items. #[derive(Debug)] pub struct MapPolicy { - order: MapOrder, - write_mode: MapWriteMode, + /// The Order of the Map + pub order: MapOrder, + /// The Map Write Mode + pub write_mode: MapWriteMode, } impl MapPolicy { @@ -188,27 +190,29 @@ impl Default for MapPolicy { } } -fn map_write_op(policy: &MapPolicy, multi: bool) -> u8 { +/// Determines the correct operation to use when setting one or more map values, depending on the +/// map policy. +pub(crate) fn map_write_op(policy: &MapPolicy, multi: bool) -> CdtMapOpType { match policy.write_mode { MapWriteMode::Update => { if multi { - CdtMapOpType::PutItems as u8 + CdtMapOpType::PutItems } else { - CdtMapOpType::Put as u8 + CdtMapOpType::Put } } MapWriteMode::UpdateOnly => { if multi { - CdtMapOpType::ReplaceItems as u8 + CdtMapOpType::ReplaceItems } else { - CdtMapOpType::Replace as u8 + CdtMapOpType::Replace } } MapWriteMode::CreateOnly => { if multi { - CdtMapOpType::AddItems as u8 + CdtMapOpType::AddItems } else { - CdtMapOpType::Add as u8 + CdtMapOpType::Add } } } @@ -244,7 +248,7 @@ pub fn set_order(bin: &str, map_order: MapOrder) -> Operation { /// /// The required map policy dictates the type of map to create when it does not exist. The map /// policy also specifies the mode used when writing items to the map. -pub fn put_item<'a>( +pub fn put<'a>( policy: &'a MapPolicy, bin: &'a str, key: &'a Value, @@ -258,7 +262,7 @@ pub fn put_item<'a>( args.push(arg); } let cdt_op = CdtOperation { - op: map_write_op(policy, false), + op: map_write_op(policy, false) as u8, encoder: Box::new(pack_cdt_op), args, }; @@ -286,7 +290,7 @@ pub fn put_items<'a>( args.push(arg); } let cdt_op = CdtOperation { - op: map_write_op(policy, true), + op: map_write_op(policy, true) as u8, encoder: Box::new(pack_cdt_op), args, }; diff --git a/src/operations/mod.rs b/src/operations/mod.rs index 881a8f59..66802135 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -19,6 +19,7 @@ pub mod bitwise; #[doc(hidden)] pub mod cdt; pub mod cdt_context; +pub mod hll; pub mod lists; pub mod maps; pub mod scalar; @@ -58,6 +59,7 @@ pub enum OperationData<'a> { CdtListOp(CdtOperation<'a>), CdtMapOp(CdtOperation<'a>), CdtBitOp(CdtOperation<'a>), + HLLOp(CdtOperation<'a>), } #[doc(hidden)] @@ -99,7 +101,8 @@ impl<'a> Operation<'a> { OperationData::Value(value) => value.estimate_size()?, OperationData::CdtListOp(ref cdt_op) | OperationData::CdtMapOp(ref cdt_op) - | OperationData::CdtBitOp(ref cdt_op) => cdt_op.estimate_size(self.ctx)?, + | OperationData::CdtBitOp(ref cdt_op) + | OperationData::HLLOp(ref cdt_op) => cdt_op.estimate_size(self.ctx)?, }; Ok(size) @@ -125,7 +128,8 @@ impl<'a> Operation<'a> { } OperationData::CdtListOp(ref cdt_op) | OperationData::CdtMapOp(ref cdt_op) - | OperationData::CdtBitOp(ref cdt_op) => { + | OperationData::CdtBitOp(ref cdt_op) + | OperationData::HLLOp(ref cdt_op) => { size += self.write_op_header_to(buffer, cdt_op.particle_type() as u8)?; size += cdt_op.write_to(buffer, self.ctx)?; } diff --git a/src/policy/batch_policy.rs b/src/policy/batch_policy.rs index 172ff694..efad30fe 100644 --- a/src/policy/batch_policy.rs +++ b/src/policy/batch_policy.rs @@ -13,6 +13,7 @@ // License for the specific language governing permissions and limitations under // the License. +use crate::expressions::FilterExpression; use crate::policy::{BasePolicy, Concurrency, PolicyLike}; /// `BatchPolicy` encapsulates parameters for all batch operations. @@ -43,6 +44,9 @@ pub struct BatchPolicy { /// /// Default: false pub send_set_name: bool, + + /// Optional Filter Expression + pub filter_expression: Option, } impl BatchPolicy { @@ -50,6 +54,11 @@ impl BatchPolicy { pub fn new() -> Self { BatchPolicy::default() } + + /// Get the current Filter Expression + pub const fn filter_expression(&self) -> &Option { + &self.filter_expression + } } impl Default for BatchPolicy { @@ -59,6 +68,7 @@ impl Default for BatchPolicy { concurrency: Concurrency::Sequential, allow_inline: true, send_set_name: false, + filter_expression: None, } } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 813d5128..ba63747a 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -46,6 +46,7 @@ pub use self::record_exists_action::RecordExistsAction; pub use self::scan_policy::ScanPolicy; pub use self::write_policy::WritePolicy; +use crate::expressions::FilterExpression; use std::option::Option; use std::time::{Duration, Instant}; @@ -143,6 +144,9 @@ pub struct BasePolicy { /// SleepBetweenReplies determines duration to sleep between retries if a /// transaction fails and the timeout was not exceeded. Enter zero to skip sleep. pub sleep_between_retries: Option, + + /// Optional FilterExpression + pub filter_expression: Option, } impl Policy for BasePolicy { @@ -150,10 +154,6 @@ impl Policy for BasePolicy { &self.priority } - fn consistency_level(&self) -> &ConsistencyLevel { - &self.consistency_level - } - fn deadline(&self) -> Option { match self.timeout { Some(timeout) => Some(Instant::now() + timeout), @@ -172,4 +172,8 @@ impl Policy for BasePolicy { fn sleep_between_retries(&self) -> Option { self.sleep_between_retries } + + fn consistency_level(&self) -> &ConsistencyLevel { + &self.consistency_level + } } diff --git a/src/policy/query_policy.rs b/src/policy/query_policy.rs index 23e0c419..f008db79 100644 --- a/src/policy/query_policy.rs +++ b/src/policy/query_policy.rs @@ -13,6 +13,7 @@ // License for the specific language governing permissions and limitations under // the License. +use crate::expressions::FilterExpression; use crate::policy::{BasePolicy, PolicyLike}; /// `QueryPolicy` encapsulates parameters for query operations. @@ -34,6 +35,9 @@ pub struct QueryPolicy { /// Terminate query if cluster is in fluctuating state. pub fail_on_cluster_change: bool, + + /// Optional Filter Expression + pub filter_expression: Option, } impl QueryPolicy { @@ -41,15 +45,21 @@ impl QueryPolicy { pub fn new() -> Self { QueryPolicy::default() } + + /// Get the current Filter Expression + pub const fn filter_expression(&self) -> &Option { + &self.filter_expression + } } impl Default for QueryPolicy { - fn default() -> QueryPolicy { + fn default() -> Self { QueryPolicy { base_policy: BasePolicy::default(), max_concurrent_nodes: 0, record_queue_size: 1024, fail_on_cluster_change: true, + filter_expression: None, } } } diff --git a/src/policy/read_policy.rs b/src/policy/read_policy.rs index f5e8733b..af2e04e7 100644 --- a/src/policy/read_policy.rs +++ b/src/policy/read_policy.rs @@ -13,6 +13,7 @@ // License for the specific language governing permissions and limitations under // the License. +use crate::expressions::FilterExpression; use crate::policy::BasePolicy; use crate::{ConsistencyLevel, Priority}; use std::time::Duration; @@ -29,6 +30,14 @@ impl Default for ReadPolicy { max_retries: Some(2), sleep_between_retries: Some(Duration::new(0, 500_000_000)), consistency_level: ConsistencyLevel::ConsistencyOne, + filter_expression: None, } } } + +impl ReadPolicy { + /// Get the Optional Filter Expression + pub const fn filter_expression(&self) -> &Option { + &self.filter_expression + } +} diff --git a/src/policy/scan_policy.rs b/src/policy/scan_policy.rs index 291fd2d1..2b0294e8 100644 --- a/src/policy/scan_policy.rs +++ b/src/policy/scan_policy.rs @@ -13,6 +13,7 @@ // License for the specific language governing permissions and limitations under // the License. +use crate::expressions::FilterExpression; use crate::policy::{BasePolicy, PolicyLike}; /// `ScanPolicy` encapsulates optional parameters used in scan operations. @@ -42,6 +43,9 @@ pub struct ScanPolicy { /// performing an operation on the socket on the server side. Zero means there is no socket /// timeout. Default: 10,000 ms. pub socket_timeout: u32, + + /// Optional Filter Expression + pub filter_expression: Option, } impl ScanPolicy { @@ -49,10 +53,15 @@ impl ScanPolicy { pub fn new() -> Self { ScanPolicy::default() } + + /// Get the current Filter Expression + pub const fn filter_expression(&self) -> &Option { + &self.filter_expression + } } impl Default for ScanPolicy { - fn default() -> ScanPolicy { + fn default() -> Self { ScanPolicy { base_policy: BasePolicy::default(), scan_percent: 100, @@ -60,6 +69,7 @@ impl Default for ScanPolicy { record_queue_size: 1024, fail_on_cluster_change: true, socket_timeout: 10000, + filter_expression: None, } } } diff --git a/src/policy/write_policy.rs b/src/policy/write_policy.rs index 73960c2f..3abad1b0 100644 --- a/src/policy/write_policy.rs +++ b/src/policy/write_policy.rs @@ -13,6 +13,7 @@ // License for the specific language governing permissions and limitations under // the License. +use crate::expressions::FilterExpression; use crate::policy::{BasePolicy, PolicyLike}; use crate::{CommitLevel, Expiration, GenerationPolicy, RecordExistsAction}; @@ -62,6 +63,9 @@ pub struct WritePolicy { /// prevents deleted records from reappearing after node failures. Valid for Aerospike Server /// Enterprise Edition 3.10+ only. pub durable_delete: bool, + + /// Optional Filter Expression + pub filter_expression: Option, } impl WritePolicy { @@ -72,10 +76,15 @@ impl WritePolicy { wp.expiration = exp; wp } + + /// Get the current Filter expression + pub const fn filter_expression(&self) -> &Option { + &self.filter_expression + } } impl Default for WritePolicy { - fn default() -> WritePolicy { + fn default() -> Self { WritePolicy { base_policy: BasePolicy::default(), record_exists_action: RecordExistsAction::Update, @@ -86,6 +95,7 @@ impl Default for WritePolicy { send_key: false, respond_per_each_op: false, durable_delete: false, + filter_expression: None, } } } diff --git a/src/query/mod.rs b/src/query/mod.rs index 13ce0774..127939bc 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -18,15 +18,12 @@ pub use self::filter::Filter; pub use self::index_types::{CollectionIndexType, IndexType}; -pub use self::predexp::*; pub use self::recordset::Recordset; pub use self::statement::Statement; pub use self::udf::UDFLang; mod filter; mod index_types; -/// Predicate Filtering -pub mod predexp; mod recordset; mod statement; mod udf; diff --git a/src/query/predexp.rs b/src/query/predexp.rs deleted file mode 100644 index 3b458307..00000000 --- a/src/query/predexp.rs +++ /dev/null @@ -1,976 +0,0 @@ -// Copyright 2015-2020 Aerospike, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::commands::buffer::Buffer; -use crate::errors::Result; - -#[doc(hidden)] -pub const _AS_PREDEXP_UNKNOWN_BIN: u16 = u16::max_value(); -#[doc(hidden)] -pub const _AS_PREDEXP_AND: u16 = 1; -#[doc(hidden)] -pub const _AS_PREDEXP_OR: u16 = 2; -#[doc(hidden)] -pub const _AS_PREDEXP_NOT: u16 = 3; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_VALUE: u16 = 10; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_VALUE: u16 = 11; -#[doc(hidden)] -pub const _AS_PREDEXP_GEOJSON_VALUE: u16 = 12; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_BIN: u16 = 100; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_BIN: u16 = 101; -#[doc(hidden)] -pub const _AS_PREDEXP_GEOJSON_BIN: u16 = 102; -#[doc(hidden)] -pub const _AS_PREDEXP_LIST_BIN: u16 = 103; -#[doc(hidden)] -pub const _AS_PREDEXP_MAP_BIN: u16 = 104; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_VAR: u16 = 120; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_VAR: u16 = 121; -#[doc(hidden)] -pub const _AS_PREDEXP_GEOJSON_VAR: u16 = 122; -#[doc(hidden)] -pub const _AS_PREDEXP_REC_DEVICE_SIZE: u16 = 150; -#[doc(hidden)] -pub const _AS_PREDEXP_REC_LAST_UPDATE: u16 = 151; -#[doc(hidden)] -pub const _AS_PREDEXP_REC_VOID_TIME: u16 = 152; -#[doc(hidden)] -pub const _AS_PREDEXP_REC_DIGEST_MODULO: u16 = 153; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_EQUAL: u16 = 200; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_UNEQUAL: u16 = 201; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_GREATER: u16 = 202; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_GREATEREQ: u16 = 203; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_LESS: u16 = 204; -#[doc(hidden)] -pub const _AS_PREDEXP_INTEGER_LESSEQ: u16 = 205; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_EQUAL: u16 = 210; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_UNEQUAL: u16 = 211; -#[doc(hidden)] -pub const _AS_PREDEXP_STRING_REGEX: u16 = 212; -#[doc(hidden)] -pub const _AS_PREDEXP_GEOJSON_WITHIN: u16 = 220; -#[doc(hidden)] -pub const _AS_PREDEXP_GEOJSON_CONTAINS: u16 = 221; -#[doc(hidden)] -pub const _AS_PREDEXP_LIST_ITERATE_OR: u16 = 250; -#[doc(hidden)] -pub const _AS_PREDEXP_MAPKEY_ITERATE_OR: u16 = 251; -#[doc(hidden)] -pub const _AS_PREDEXP_MAPVAL_ITERATE_OR: u16 = 252; -#[doc(hidden)] -pub const _AS_PREDEXP_LIST_ITERATE_AND: u16 = 253; -#[doc(hidden)] -pub const _AS_PREDEXP_MAPKEY_ITERATE_AND: u16 = 254; -#[doc(hidden)] -pub const _AS_PREDEXP_MAPVAL_ITERATE_AND: u16 = 255; - -#[doc(hidden)] -pub trait PredExp: Send + Sync { - // Returns String Value of the Predicate action - fn pred_string(&self) -> String; - // Returns the absolute size of the Predicate (default_size + additional-size) - fn marshaled_size(&self) -> usize; - // Writes the PredExp to the Command Buffer - fn write(&self, buffer: &mut Buffer) -> Result<()>; - - // Default Header Size - #[doc(hidden)] - fn default_size(&self) -> usize { - 2 + 4 // size of TAG + size of LEN - } - - // Write tag und len to buffer - #[doc(hidden)] - fn write_head(&self, buffer: &mut Buffer, tag: u16, len: u32) -> Result<()> { - buffer.write_u16(tag)?; - buffer.write_u32(len)?; - Ok(()) - } -} - -// ------------------------------------- PredExpAnd - -/// Predicate for And -#[derive(Debug, Clone)] -pub struct PredExpAnd { - /// Number of Predicates - pub nexpr: u16, -} - -impl PredExp for PredExpAnd { - fn pred_string(&self) -> String { - String::from("AND") - } - - fn marshaled_size(&self) -> usize { - self.default_size() + 2 - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_AND, 2)?; - buffer.write_u16(self.nexpr)?; - Ok(()) - } -} - -/// Create "AND" Predicate -#[macro_export] -macro_rules! as_predexp_and { - ($nexpr:expr) => {{ - $crate::query::predexp::PredExpAnd { nexpr: $nexpr } - }}; -} - -// ------------------------------------- PredExpOr - -/// Predicate for Or -#[derive(Debug, Clone)] -pub struct PredExpOr { - /// Number of Predicates - pub nexpr: u16, -} - -impl PredExp for PredExpOr { - fn pred_string(&self) -> String { - String::from("OR") - } - - fn marshaled_size(&self) -> usize { - self.default_size() + 2 - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_OR, 2)?; - buffer.write_u16(self.nexpr)?; - Ok(()) - } -} - -/// Create "OR" Predicate -#[macro_export] -macro_rules! as_predexp_or { - ($nexpr:expr) => {{ - $crate::query::predexp::PredExpOr { nexpr: $nexpr } - }}; -} - -// ------------------------------------- PredExpNot - -/// Predicate for Negation -#[derive(Debug, Clone)] -pub struct PredExpNot {} - -impl PredExp for PredExpNot { - fn pred_string(&self) -> String { - String::from("NOT") - } - - fn marshaled_size(&self) -> usize { - self.default_size() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_NOT, 0)?; - Ok(()) - } -} - -/// Create "NOT" Predicate -#[macro_export] -macro_rules! as_predexp_not { - () => {{ - $crate::query::predexp::PredExpNot {} - }}; -} - -// ------------------------------------- PredExpIntegerValue - -/// Predicate for Integer Values -#[derive(Debug, Clone)] -pub struct PredExpIntegerValue { - /// Value - pub val: i64, -} - -impl PredExp for PredExpIntegerValue { - fn pred_string(&self) -> String { - self.val.to_string() - } - - fn marshaled_size(&self) -> usize { - self.default_size() + 8 - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_INTEGER_VALUE, 8)?; - buffer.write_i64(self.val)?; - Ok(()) - } -} - -/// Create Integer Value Predicate -#[macro_export] -macro_rules! as_predexp_integer_value { - ($val:expr) => {{ - $crate::query::predexp::PredExpIntegerValue { val: $val } - }}; -} - -// ------------------------------------- PredExpStringValue - -/// Predicate for Integer Values -#[derive(Debug, Clone)] -pub struct PredExpStringValue { - /// Value - pub val: String, -} - -impl PredExp for PredExpStringValue { - fn pred_string(&self) -> String { - self.val.clone() - } - - fn marshaled_size(&self) -> usize { - self.default_size() + self.val.len() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_STRING_VALUE, self.val.len() as u32)?; - buffer.write_str(&self.val)?; - Ok(()) - } -} - -/// Create String Value Predicate -#[macro_export] -macro_rules! as_predexp_string_value { - ($val:expr) => {{ - $crate::query::predexp::PredExpStringValue { val: $val } - }}; -} - -// ------------------------------------- PredExpGeoJSONValue - -/// Predicate for `GeoJSON` Values -#[derive(Debug, Clone)] -pub struct PredExpGeoJSONValue { - /// Value - pub val: String, -} - -impl PredExp for PredExpGeoJSONValue { - fn pred_string(&self) -> String { - self.val.clone() - } - - fn marshaled_size(&self) -> usize { - self.default_size() - + 1 // flags - + 2 // ncells - + self.val.len() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head( - buffer, - _AS_PREDEXP_GEOJSON_VALUE, - (1 + 2 + self.val.len()) as u32, - )?; - buffer.write_u8(0u8)?; - buffer.write_u16(0)?; - buffer.write_str(&self.val)?; - Ok(()) - } -} - -/// Create GeoJSON Value Predicate -#[macro_export] -macro_rules! as_predexp_geojson_value { - ($val:expr) => {{ - $crate::query::predexp::PredExpGeoJSONValue { val: $val } - }}; -} - -// ------------------------------------- PredExpBin - -/// Predicate for Bins -#[derive(Debug, Clone)] -pub struct PredExpBin { - /// Bin Name - pub name: String, - /// Bin Type - pub tag: u16, -} - -impl PredExp for PredExpBin { - fn pred_string(&self) -> String { - self.name.clone() - } - - fn marshaled_size(&self) -> usize { - self.default_size() + self.name.len() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, self.tag, (self.name.len()) as u32)?; - buffer.write_str(&self.name)?; - Ok(()) - } -} - -/// Create Unknown Bin Predicate -#[macro_export] -macro_rules! as_predexp_unknown_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_UNKNOWN_BIN, - } - }}; -} - -/// Create Integer Bin Predicate -#[macro_export] -macro_rules! as_predexp_integer_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_BIN, - } - }}; -} - -/// Create String Bin Predicate -#[macro_export] -macro_rules! as_predexp_string_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_STRING_BIN, - } - }}; -} - -/// Create GeoJSON Bin Predicate -#[macro_export] -macro_rules! as_predexp_geojson_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_GEOJSON_BIN, - } - }}; -} - -/// Create List Bin Predicate -#[macro_export] -macro_rules! as_predexp_list_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_LIST_BIN, - } - }}; -} - -/// Create Map Bin Predicate -#[macro_export] -macro_rules! as_predexp_map_bin { - ($name:expr) => {{ - $crate::query::predexp::PredExpBin { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_MAP_BIN, - } - }}; -} - -// ------------------------------------- PredExpVar - -/// Predicate for Vars -#[derive(Debug, Clone)] -pub struct PredExpVar { - /// Var Name - pub name: String, - /// Var Type - pub tag: u16, -} - -impl PredExp for PredExpVar { - fn pred_string(&self) -> String { - self.name.clone() - } - - fn marshaled_size(&self) -> usize { - self.default_size() + self.name.len() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, self.tag, (self.name.len()) as u32)?; - buffer.write_str(&self.name)?; - Ok(()) - } -} - -/// Create 64Bit Integer Var used in list/map iterations -#[macro_export] -macro_rules! as_predexp_integer_var { - ($name:expr) => {{ - $crate::query::predexp::PredExpVar { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_VAR, - } - }}; -} - -/// Create String Var used in list/map iterations -#[macro_export] -macro_rules! as_predexp_string_var { - ($name:expr) => {{ - $crate::query::predexp::PredExpVar { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_STRING_VAR, - } - }}; -} - -/// Create String Var used in list/map iterations -#[macro_export] -macro_rules! as_predexp_geojson_var { - ($name:expr) => {{ - $crate::query::predexp::PredExpVar { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_GEOJSON_VAR, - } - }}; -} - -// ------------------------------------- PredExpMD - -/// Predicate for `MetaData` (`RecDeviceSize`, `RecLastUpdate`, `RecVoidTime`) -#[derive(Debug, Clone)] -pub struct PredExpMD { - /// Predicate Type - pub tag: u16, // not marshaled -} - -impl PredExp for PredExpMD { - fn pred_string(&self) -> String { - match self.tag { - _AS_PREDEXP_REC_DEVICE_SIZE => String::from("rec.DeviceSize"), - _AS_PREDEXP_REC_LAST_UPDATE => String::from("rec.LastUpdate"), - _AS_PREDEXP_REC_VOID_TIME => String::from("rec.Expiration"), - _AS_PREDEXP_REC_DIGEST_MODULO => String::from("rec.DigestModulo"), - _ => panic!("Invalid Metadata tag."), - } - } - - fn marshaled_size(&self) -> usize { - self.default_size() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, self.tag, 0)?; - Ok(()) - } -} - -/// Create Record Size on Disk Predicate -#[macro_export] -macro_rules! as_predexp_rec_device_size { - () => {{ - $crate::query::predexp::PredExpMD { - tag: $crate::query::predexp::_AS_PREDEXP_REC_DEVICE_SIZE, - } - }}; -} - -/// Create Last Update Predicate -#[macro_export] -macro_rules! as_predexp_rec_last_update { - () => {{ - $crate::query::predexp::PredExpMD { - tag: $crate::query::predexp::_AS_PREDEXP_REC_LAST_UPDATE, - } - }}; -} - -/// Create Record Expiration Time Predicate in nanoseconds since 1970-01-01 epoch as 64 bit integer -#[macro_export] -macro_rules! as_predexp_rec_void_time { - () => {{ - $crate::query::predexp::PredExpMD { - tag: $crate::query::predexp::_AS_PREDEXP_REC_VOID_TIME, - } - }}; -} - -// ------------------------------------- PredExpMDDigestModulo - -/// Predicate Digest Modulo Metadata Value -/// The digest modulo expression assumes the value of 4 bytes of the -// record's key digest modulo as its argument. -// This predicate is available in Aerospike server versions 3.12.1+ -#[derive(Debug, Clone)] -pub struct PredExpMDDigestModulo { - /// Modulo - pub modulo: i32, // not marshaled -} - -impl PredExp for PredExpMDDigestModulo { - fn pred_string(&self) -> String { - String::from("rec.DigestModulo") - } - - fn marshaled_size(&self) -> usize { - self.default_size() + 4 - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_REC_DIGEST_MODULO, 4)?; - buffer.write_i32(self.modulo)?; - Ok(()) - } -} - -/// Creates a digest modulo record metadata value predicate expression. -#[macro_export] -macro_rules! as_predexp_rec_digest_modulo { - ($modulo:expr) => {{ - $crate::query::predexp::PredExpMDDigestModulo { modulo: $modulo } - }}; -} - -// ------------------------------------- PredExpCompare - -/// Predicate for comparing -#[derive(Debug, Clone)] -pub struct PredExpCompare { - /// Compare Type - pub tag: u16, // not marshaled -} - -impl PredExp for PredExpCompare { - fn pred_string(&self) -> String { - match self.tag { - _AS_PREDEXP_INTEGER_EQUAL | _AS_PREDEXP_STRING_EQUAL => String::from("="), - _AS_PREDEXP_INTEGER_UNEQUAL | _AS_PREDEXP_STRING_UNEQUAL => String::from("!="), - _AS_PREDEXP_INTEGER_GREATER => String::from(">"), - _AS_PREDEXP_INTEGER_GREATEREQ => String::from(">="), - _AS_PREDEXP_INTEGER_LESS => String::from("<"), - _AS_PREDEXP_INTEGER_LESSEQ => String::from("<="), - _AS_PREDEXP_STRING_REGEX => String::from("~="), - _AS_PREDEXP_GEOJSON_CONTAINS => String::from("CONTAINS"), - _AS_PREDEXP_GEOJSON_WITHIN => String::from("WITHIN"), - _ => panic!("unexpected predicate tag"), - } - } - - fn marshaled_size(&self) -> usize { - self.default_size() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, self.tag, 0)?; - Ok(()) - } -} - -/// Creates Equal predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_equal { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_EQUAL, - } - }}; -} - -/// Creates NotEqual predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_unequal { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_UNEQUAL, - } - }}; -} - -/// Creates Greater Than predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_greater { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_GREATER, - } - }}; -} - -/// Creates Greater Than Or Equal predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_greatereq { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_GREATEREQ, - } - }}; -} - -/// Creates Less Than predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_less { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_LESS, - } - }}; -} - -/// Creates Less Than Or Equal predicate for integer values -#[macro_export] -macro_rules! as_predexp_integer_lesseq { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_INTEGER_LESSEQ, - } - }}; -} - -/// Creates Equal predicate for string values -#[macro_export] -macro_rules! as_predexp_string_equal { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_STRING_EQUAL, - } - }}; -} - -/// Creates Not Equal predicate for string values -#[macro_export] -macro_rules! as_predexp_string_unequal { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_STRING_UNEQUAL, - } - }}; -} - -/// Creates Within Region predicate for `GeoJSON` values -#[macro_export] -macro_rules! as_predexp_geojson_within { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_GEOJSON_WITHIN, - } - }}; -} - -/// Creates Region Contains predicate for GeoJSON values -#[macro_export] -macro_rules! as_predexp_geojson_contains { - () => {{ - $crate::query::predexp::PredExpCompare { - tag: $crate::query::predexp::_AS_PREDEXP_GEOJSON_CONTAINS, - } - }}; -} - -// ------------------------------------- PredExpStringRegex - -/// Predicate for String Regex -#[derive(Debug, Clone)] -pub struct PredExpStringRegex { - /// Flags - pub cflags: u32, // not marshaled -} - -impl PredExp for PredExpStringRegex { - fn pred_string(&self) -> String { - String::from("regex:") - } - - fn marshaled_size(&self) -> usize { - self.default_size() + 4 - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_STRING_REGEX, 4)?; - buffer.write_u32(self.cflags)?; - Ok(()) - } -} - -/// Creates a Regex predicate -#[macro_export] -macro_rules! as_predexp_string_regex { - ($cflags:expr) => {{ - $crate::query::predexp::PredExpStringRegex { cflags: $cflags } - }}; -} - -// ------------------------------------- PredExp???Iterate??? - -/// Predicate for Iterators -#[derive(Debug, Clone)] -pub struct PredExpIter { - /// Name - pub name: String, - /// Iter Type - pub tag: u16, // not marshaled -} - -impl PredExp for PredExpIter { - fn pred_string(&self) -> String { - match self.tag { - _AS_PREDEXP_LIST_ITERATE_OR => { - let mut tagname = String::from("list_iterate_or using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _AS_PREDEXP_MAPKEY_ITERATE_OR => { - let mut tagname = String::from("mapkey_iterate_or using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _AS_PREDEXP_MAPVAL_ITERATE_OR => { - let mut tagname = String::from("mapval_iterate_or using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _AS_PREDEXP_LIST_ITERATE_AND => { - let mut tagname = String::from("list_iterate_and using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _AS_PREDEXP_MAPKEY_ITERATE_AND => { - let mut tagname = String::from("mapkey_iterate_and using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _AS_PREDEXP_MAPVAL_ITERATE_AND => { - let mut tagname = String::from("mapvalue_iterate_and using \""); - tagname.push_str(&self.name); - tagname.push_str("\":"); - tagname - } - _ => panic!("Invalid Metadata tag."), - } - } - - fn marshaled_size(&self) -> usize { - self.default_size() + self.name.len() - } - - fn write(&self, buffer: &mut Buffer) -> Result<()> { - self.write_head(buffer, _AS_PREDEXP_STRING_REGEX, self.name.len() as u32)?; - buffer.write_str(&self.name)?; - Ok(()) - } -} - -/// Creates an Or iterator predicate for list items -#[macro_export] -macro_rules! as_predexp_list_iterate_or { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_LIST_ITERATE_OR, - } - }}; -} - -/// Creates an And iterator predicate for list items -#[macro_export] -macro_rules! as_predexp_list_iterate_and { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_LIST_ITERATE_AND, - } - }}; -} - -/// Creates an Or iterator predicate on map keys -#[macro_export] -macro_rules! as_predexp_mapkey_iterate_or { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_MAPKEY_ITERATE_OR, - } - }}; -} - -/// Creates an And iterator predicate on map keys -#[macro_export] -macro_rules! as_predexp_mapkey_iterate_and { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_MAPKEY_ITERATE_AND, - } - }}; -} - -/// Creates an Or iterator predicate on map values -#[macro_export] -macro_rules! as_predexp_mapval_iterate_or { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_MAPVAL_ITERATE_OR, - } - }}; -} - -/// Creates an And iterator predicate on map values -#[macro_export] -macro_rules! as_predexp_mapval_iterate_and { - ($name:expr) => {{ - $crate::query::predexp::PredExpIter { - name: $name, - tag: $crate::query::predexp::_AS_PREDEXP_MAPVAL_ITERATE_AND, - } - }}; -} - -#[cfg(test)] -mod tests { - use crate::query::predexp::*; - - #[test] - fn predicate_macros() { - let pred_and = as_predexp_and!(2); - assert_eq!(pred_and.pred_string(), "AND"); - assert_eq!(pred_and.nexpr, 2); - - let pred_or = as_predexp_or!(2); - assert_eq!(pred_or.pred_string(), "OR"); - assert_eq!(pred_or.nexpr, 2); - - let pred_not = as_predexp_not!(); - assert_eq!(pred_not.pred_string(), "NOT"); - - let pred_int_val = as_predexp_integer_value!(2); - assert_eq!(pred_int_val.pred_string(), "2"); - assert_eq!(pred_int_val.val, 2); - - let pred_str_val = as_predexp_string_value!(String::from("test")); - assert_eq!(pred_str_val.pred_string(), "test"); - assert_eq!(pred_str_val.val, "test"); - - let pred_geo_val = as_predexp_geojson_value!(String::from("test")); - assert_eq!(pred_geo_val.pred_string(), "test"); - assert_eq!(pred_geo_val.val, "test"); - - let bin_unknown = as_predexp_unknown_bin!(String::from("test")); - assert_eq!(bin_unknown.pred_string(), "test"); - assert_eq!(bin_unknown.tag, _AS_PREDEXP_UNKNOWN_BIN); - - let int_bin = as_predexp_integer_bin!(String::from("test")); - assert_eq!(int_bin.pred_string(), "test"); - assert_eq!(int_bin.tag, _AS_PREDEXP_INTEGER_BIN); - - let str_bin = as_predexp_string_bin!(String::from("test")); - assert_eq!(str_bin.pred_string(), "test"); - assert_eq!(str_bin.tag, _AS_PREDEXP_STRING_BIN); - - let geo_bin = as_predexp_geojson_bin!(String::from("test")); - assert_eq!(geo_bin.pred_string(), "test"); - assert_eq!(geo_bin.tag, _AS_PREDEXP_GEOJSON_BIN); - - let list_bin = as_predexp_list_bin!(String::from("test")); - assert_eq!(list_bin.pred_string(), "test"); - assert_eq!(list_bin.tag, _AS_PREDEXP_LIST_BIN); - - let map_bin = as_predexp_map_bin!(String::from("test")); - assert_eq!(map_bin.pred_string(), "test"); - assert_eq!(map_bin.tag, _AS_PREDEXP_MAP_BIN); - - let int_var = as_predexp_integer_var!(String::from("test")); - assert_eq!(int_var.pred_string(), "test"); - assert_eq!(int_var.tag, _AS_PREDEXP_INTEGER_VAR); - - let str_var = as_predexp_string_var!(String::from("test")); - assert_eq!(str_var.pred_string(), "test"); - assert_eq!(str_var.tag, _AS_PREDEXP_STRING_VAR); - - let geo_var = as_predexp_geojson_var!(String::from("test")); - assert_eq!(geo_var.pred_string(), "test"); - assert_eq!(geo_var.tag, _AS_PREDEXP_GEOJSON_VAR); - - let dev_size = as_predexp_rec_device_size!(); - assert_eq!(dev_size.tag, _AS_PREDEXP_REC_DEVICE_SIZE); - - let last_update = as_predexp_rec_last_update!(); - assert_eq!(last_update.tag, _AS_PREDEXP_REC_LAST_UPDATE); - - let void_time = as_predexp_rec_void_time!(); - assert_eq!(void_time.tag, _AS_PREDEXP_REC_VOID_TIME); - - let digest_modulo = as_predexp_rec_digest_modulo!(10); - assert_eq!(digest_modulo.modulo, 10); - - let int_eq = as_predexp_integer_equal!(); - assert_eq!(int_eq.tag, _AS_PREDEXP_INTEGER_EQUAL); - - let int_uneq = as_predexp_integer_unequal!(); - assert_eq!(int_uneq.tag, _AS_PREDEXP_INTEGER_UNEQUAL); - - let int_gt = as_predexp_integer_greater!(); - assert_eq!(int_gt.tag, _AS_PREDEXP_INTEGER_GREATER); - - let int_gteq = as_predexp_integer_greatereq!(); - assert_eq!(int_gteq.tag, _AS_PREDEXP_INTEGER_GREATEREQ); - - let int_lt = as_predexp_integer_less!(); - assert_eq!(int_lt.tag, _AS_PREDEXP_INTEGER_LESS); - - let int_lteq = as_predexp_integer_lesseq!(); - assert_eq!(int_lteq.tag, _AS_PREDEXP_INTEGER_LESSEQ); - - let str_eq = as_predexp_string_equal!(); - assert_eq!(str_eq.tag, _AS_PREDEXP_STRING_EQUAL); - - let str_uneq = as_predexp_string_unequal!(); - assert_eq!(str_uneq.tag, _AS_PREDEXP_STRING_UNEQUAL); - - let geo_within = as_predexp_geojson_within!(); - assert_eq!(geo_within.tag, _AS_PREDEXP_GEOJSON_WITHIN); - - let geo_contains = as_predexp_geojson_contains!(); - assert_eq!(geo_contains.tag, _AS_PREDEXP_GEOJSON_CONTAINS); - - let string_reg = as_predexp_string_regex!(5); - assert_eq!(string_reg.cflags, 5); - } -} diff --git a/src/query/statement.rs b/src/query/statement.rs index e62a9010..192feafb 100644 --- a/src/query/statement.rs +++ b/src/query/statement.rs @@ -14,11 +14,9 @@ // the License. use crate::errors::{ErrorKind, Result}; -use crate::query::predexp::PredExp; use crate::query::Filter; use crate::Bins; use crate::Value; -use std::sync::Arc; #[derive(Clone)] pub struct Aggregation { @@ -47,9 +45,6 @@ pub struct Statement { /// Optional Lua aggregation function parameters. pub aggregation: Option, - - /// Predicate Filter - pub predexp: Vec>>, } impl Statement { @@ -74,7 +69,6 @@ impl Statement { index_name: None, aggregation: None, filters: None, - predexp: Vec::new(), } } @@ -102,25 +96,6 @@ impl Statement { } } - /// Add a Predicate filter to the statement. - /// - /// # Example - /// - /// This Example uses a simple predexp Filter to find all records in namespace _foo_ and set _bar_ - /// where the _age_ Bin is equal to 32. - /// - /// ```rust - /// # use aerospike::*; - /// - /// let mut stmt = Statement::new("foo", "bar", Bins::from(["name", "age"])); - /// stmt.add_predicate(as_predexp_integer_bin!("age".to_string())); - /// stmt.add_predicate(as_predexp_integer_value!(32)); - /// stmt.add_predicate(as_predexp_integer_equal!()); - /// ``` - pub fn add_predicate(&mut self, predicate: S) { - self.predexp.push(Arc::new(Box::new(predicate))); - } - /// Set Lua aggregation function parameters. pub fn set_aggregate_function( &mut self, diff --git a/src/value.rs b/src/value.rs index 9f8c3f93..6078fb57 100644 --- a/src/value.rs +++ b/src/value.rs @@ -190,6 +190,9 @@ pub enum Value { /// GeoJSON data type are JSON formatted strings to encode geospatial information. GeoJSON(String), + + /// HLL value + HLL(Vec), } #[allow(clippy::derive_hash_xor_eq)] @@ -205,7 +208,7 @@ impl Hash for Value { Value::UInt(ref val) => val.hash(state), Value::Float(ref val) => val.hash(state), Value::String(ref val) | Value::GeoJSON(ref val) => val.hash(state), - Value::Blob(ref val) => val.hash(state), + Value::Blob(ref val) | Value::HLL(ref val) => val.hash(state), Value::List(ref val) => val.hash(state), Value::HashMap(_) => panic!("HashMaps cannot be used as map keys."), Value::OrderedMap(_) => panic!("OrderedMaps cannot be used as map keys."), @@ -240,6 +243,7 @@ impl Value { Value::HashMap(_) => ParticleType::MAP, Value::OrderedMap(_) => panic!("The library never passes ordered maps to the server."), Value::GeoJSON(_) => ParticleType::GEOJSON, + Value::HLL(_) => ParticleType::HLL, } } @@ -252,7 +256,7 @@ impl Value { Value::Bool(ref val) => val.to_string(), Value::Float(ref val) => val.to_string(), Value::String(ref val) | Value::GeoJSON(ref val) => val.to_string(), - Value::Blob(ref val) => format!("{:?}", val), + Value::Blob(ref val) | Value::HLL(ref val) => format!("{:?}", val), Value::List(ref val) => format!("{:?}", val), Value::HashMap(ref val) => format!("{:?}", val), Value::OrderedMap(ref val) => format!("{:?}", val), @@ -275,6 +279,7 @@ impl Value { Value::List(_) | Value::HashMap(_) => encoder::pack_value(&mut None, self), Value::OrderedMap(_) => panic!("The library never passes ordered maps to the server."), Value::GeoJSON(ref s) => Ok(1 + 2 + s.len()), // flags + ncells + jsonstr + Value::HLL(ref h) => Ok(h.len()), } } @@ -292,7 +297,7 @@ impl Value { Value::Bool(ref val) => buf.write_bool(*val), Value::Float(ref val) => buf.write_f64(f64::from(val)), Value::String(ref val) => buf.write_str(val), - Value::Blob(ref val) => buf.write_bytes(val), + Value::Blob(ref val) | Value::HLL(ref val) => buf.write_bytes(val), Value::List(_) | Value::HashMap(_) => encoder::pack_value(&mut Some(buf), self), Value::OrderedMap(_) => panic!("The library never passes ordered maps to the server."), Value::GeoJSON(ref val) => buf.write_geo(val), @@ -589,6 +594,7 @@ pub fn bytes_to_particle(ptype: u8, buf: &mut Buffer, len: usize) -> Result Ok(Value::from("A DIGEST, NOT IMPLEMENTED YET!")), ParticleType::LDT => Ok(Value::from("A LDT, NOT IMPLEMENTED YET!")), + ParticleType::HLL => Ok(Value::HLL(buf.read_blob(len)?)), } } @@ -751,6 +757,7 @@ impl Serialize for Value { } map.end() } + Value::HLL(b) => serializer.serialize_bytes(&b[..]), } } } diff --git a/tests/src/cdt_map.rs b/tests/src/cdt_map.rs index f3f9790b..6cc31e49 100644 --- a/tests/src/cdt_map.rs +++ b/tests/src/cdt_map.rs @@ -50,7 +50,7 @@ fn map_operations() { client.put(&wpolicy, &key, &bins).unwrap(); let (k, v) = (as_val!("c"), as_val!(3)); - let op = maps::put_item(&mpolicy, bin_name, &k, &v); + let op = maps::put(&mpolicy, bin_name, &k, &v); let rec = client.operate(&wpolicy, &key, &[op]).unwrap(); // returns size of map after put assert_eq!(*rec.bins.get(bin_name).unwrap(), as_val!(3)); @@ -292,7 +292,7 @@ fn map_operations() { let mkey = as_val!("ctxtest"); let mval = as_map!("x" => 7, "y" => 8, "z" => 9); - let op = maps::put_item(&mpolicy, bin_name, &mkey, &mval); + let op = maps::put(&mpolicy, bin_name, &mkey, &mval); client.operate(&wpolicy, &key, &[op]).unwrap(); let ctx = &vec![ctx_map_key(mkey)]; diff --git a/tests/src/exp.rs b/tests/src/exp.rs new file mode 100644 index 00000000..b7d4f14c --- /dev/null +++ b/tests/src/exp.rs @@ -0,0 +1,555 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +use crate::common; +use env_logger; + +use aerospike::expressions::*; +use aerospike::ParticleType; +use aerospike::*; +use std::sync::Arc; + +const EXPECTED: usize = 100; + +fn create_test_set(no_records: usize) -> String { + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..no_records as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", i); + let sbin = as_bin!("bin2", format!("{}", i)); + let fbin = as_bin!("bin3", i as f64 / 3 as f64); + let str = format!("{}{}", "blob", i); + let bbin = as_bin!("bin4", str.as_bytes()); + let lbin = as_bin!("bin5", as_list!("a", "b", i)); + let mbin = as_bin!("bin6", as_map!("a" => "test", "b" => i)); + let bins = vec![&ibin, &sbin, &fbin, &bbin, &lbin, &mbin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + } + + set_name +} + +#[test] +fn expression_compare() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // EQ + let rs = test_filter( + eq( + int_bin("bin".to_string()), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "EQ Test Failed"); + + // NE + let rs = test_filter( + ne( + int_bin("bin".to_string()), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "NE Test Failed"); + + // LT + let rs = test_filter( + lt( + int_bin("bin".to_string()), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 10, "LT Test Failed"); + + // LE + let rs = test_filter( + le( + int_bin("bin".to_string()), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 11, "LE Test Failed"); + + // GT + let rs = test_filter( + gt( + int_bin("bin".to_string()), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "GT Test Failed"); + + // GE + let rs = test_filter( + ge( + int_bin("bin".to_string()), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "GT Test Failed"); +} + +#[test] +fn expression_condition() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // AND + let rs = test_filter( + and(vec![ + eq( + int_bin("bin".to_string()), + int_val(1), + ), + eq( + string_bin("bin2".to_string()), + string_val("1".to_string()), + ), + ]), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "AND Test Failed"); + + // OR + let rs = test_filter( + or(vec![ + eq( + int_bin("bin".to_string()), + int_val(1), + ), + eq( + int_bin("bin".to_string()), + int_val(3), + ), + ]), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 2, "OR Test Failed"); + + // NOT + let rs = test_filter( + not(eq( + int_bin("bin".to_string()), + int_val(1), + )), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "NOT Test Failed"); +} + +#[test] +fn expression_data_types() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // INT + let rs = test_filter( + eq( + int_bin("bin".to_string()), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "INT Test Failed"); + + // STRING + let rs = test_filter( + eq( + string_bin("bin2".to_string()), + string_val("1".to_string()), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "STRING Test Failed"); + + let rs = test_filter( + eq( + float_bin("bin3".to_string()), + float_val(2f64), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "FLOAT Test Failed"); + + let rs = test_filter( + eq( + blob_bin("bin4".to_string()), + blob_val(format!("{}{}", "blob", 5).into_bytes()), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "BLOB Test Failed"); + + let rs = test_filter( + ne( + bin_type("bin".to_string()), + int_val(ParticleType::NULL as i64), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "BIN TYPE Test Failed"); +} + +#[test] +fn expression_rec_ops() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // dev size 0 because in-memory + let rs = test_filter( + le(device_size(), int_val(0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "DEVICE SIZE Test Failed"); + + let rs = test_filter( + gt(last_update(), int_val(15000)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "LAST UPDATE Test Failed"); + + let rs = test_filter( + gt(since_update(), int_val(150)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "SINCE UPDATE Test Failed"); + + // Records dont expire + let rs = test_filter( + le(void_time(), int_val(0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "VOID TIME Test Failed"); + + let rs = test_filter( + le(ttl(), int_val(0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "TTL Test Failed"); + + let rs = test_filter(not(is_tombstone()), &set_name); + let count = count_results(rs); + assert_eq!(count, 100, "TOMBSTONE Test Failed"); + + let rs = test_filter( + eq( + expressions::set_name(), + string_val(set_name.clone()), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "SET NAME Test Failed"); + + let rs = test_filter(bin_exists("bin4".to_string()), &set_name); + let count = count_results(rs); + assert_eq!(count, 100, "BIN EXISTS Test Failed"); + + let rs = test_filter( + eq(digest_modulo(3), int_val(1)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count > 0 && count < 100, true, "DIGEST MODULO Test Failed"); + + let rs = test_filter( + eq(key(ExpType::INT), int_val(50)), + &set_name, + ); + let count = count_results(rs); + // 0 because key is not saved + assert_eq!(count, 0, "KEY Test Failed"); + + let rs = test_filter(key_exists(), &set_name); + let count = count_results(rs); + // 0 because key is not saved + assert_eq!(count, 0, "KEY EXISTS Test Failed"); + + let rs = test_filter( + eq(nil(), nil()), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "NIL Test Failed"); + + let rs = test_filter( + regex_compare( + "[1-5]".to_string(), + RegexFlag::ICASE as i64, + string_bin("bin2".to_string()), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 75, "REGEX Test Failed"); +} + +#[test] +fn expression_commands() { + let _ = env_logger::try_init(); + + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..EXPECTED as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", i); + + let bins = vec![&ibin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + } + let mut wpolicy = WritePolicy::default(); + let mut rpolicy = ReadPolicy::default(); + let mut spolicy = ScanPolicy::default(); + let mut bpolicy = BatchPolicy::default(); + + // DELETE + let key = as_key!(namespace, &set_name, 15); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(16), + )); + let test = client.delete(&wpolicy, &key); + assert_eq!(test.is_err(), true, "DELETE EXP Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.delete(&wpolicy, &key); + assert_eq!(test.is_ok(), true, "DELETE EXP Ok Test Failed"); + + // PUT + let key = as_key!(namespace, &set_name, 25); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.put(&wpolicy, &key, &[as_bin!("bin", 26)]); + assert_eq!(test.is_err(), true, "PUT Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(25), + )); + let test = client.put(&wpolicy, &key, &[as_bin!("bin", 26)]); + assert_eq!(test.is_ok(), true, "PUT Ok Test Failed"); + + // GET + let key = as_key!(namespace, &set_name, 35); + rpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.get(&rpolicy, &key, Bins::All); + assert_eq!(test.is_err(), true, "GET Err Test Failed"); + + rpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(35), + )); + let test = client.get(&rpolicy, &key, Bins::All); + assert_eq!(test.is_ok(), true, "GET Ok Test Failed"); + + // EXISTS + let key = as_key!(namespace, &set_name, 45); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.exists(&wpolicy, &key); + assert_eq!(test.is_err(), true, "EXISTS Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(45), + )); + let test = client.exists(&wpolicy, &key); + assert_eq!(test.is_ok(), true, "EXISTS Ok Test Failed"); + + // APPEND + let key = as_key!(namespace, &set_name, 55); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.add(&wpolicy, &key, &[as_bin!("test55", "test")]); + assert_eq!(test.is_err(), true, "APPEND Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(55), + )); + let test = client.add(&wpolicy, &key, &[as_bin!("test55", "test")]); + assert_eq!(test.is_ok(), true, "APPEND Ok Test Failed"); + + // PREPEND + let key = as_key!(namespace, &set_name, 55); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.prepend(&wpolicy, &key, &[as_bin!("test55", "test")]); + assert_eq!(test.is_err(), true, "PREPEND Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(55), + )); + let test = client.prepend(&wpolicy, &key, &[as_bin!("test55", "test")]); + assert_eq!(test.is_ok(), true, "PREPEND Ok Test Failed"); + + // TOUCH + let key = as_key!(namespace, &set_name, 65); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let test = client.touch(&wpolicy, &key); + assert_eq!(test.is_err(), true, "TOUCH Err Test Failed"); + + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(65), + )); + let test = client.touch(&wpolicy, &key); + assert_eq!(test.is_ok(), true, "TOUCH Ok Test Failed"); + + // SCAN + spolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(75), + )); + match client.scan(&spolicy, namespace, &set_name, Bins::All) { + Ok(records) => { + let mut count = 0; + for record in &*records { + match record { + Ok(_) => count += 1, + Err(err) => panic!("Error executing scan: {}", err), + } + } + assert_eq!(count, 1, "SCAN Test Failed"); + } + Err(err) => println!("Failed to execute scan: {}", err), + } + + // OPERATE + let bin = as_bin!("test85", 85); + let ops = vec![operations::add(&bin)]; + + let key = as_key!(namespace, &set_name, 85); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(15), + )); + let op = client.operate(&wpolicy, &key, &ops); + assert_eq!(op.is_err(), true, "OPERATE Err Test Failed"); + + let key = as_key!(namespace, &set_name, 85); + wpolicy.filter_expression = Some(eq( + int_bin("bin".to_string()), + int_val(85), + )); + let op = client.operate(&wpolicy, &key, &ops); + assert_eq!(op.is_ok(), true, "OPERATE Ok Test Failed"); + + // BATCH GET + let mut batch_reads = vec![]; + let b = Bins::from(["bin"]); + for i in 85..90 { + let key = as_key!(namespace, &set_name, i); + batch_reads.push(BatchRead::new(key, &b)); + } + bpolicy.filter_expression = Some(gt( + int_bin("bin".to_string()), + int_val(84), + )); + match client.batch_get(&bpolicy, batch_reads) { + Ok(results) => { + for result in results { + let mut count = 0; + match result.record { + Some(_) => count += 1, + None => {} + } + assert_eq!(count, 1, "BATCH GET Ok Test Failed") + } + } + Err(err) => panic!("Error executing batch request: {}", err), + } +} + +fn test_filter(filter: FilterExpression, set_name: &str) -> Arc { + let client = common::client(); + let namespace = common::namespace(); + + let mut qpolicy = QueryPolicy::default(); + qpolicy.filter_expression = Some(filter); + + let statement = Statement::new(namespace, set_name, Bins::All); + client.query(&qpolicy, statement).unwrap() +} + +fn count_results(rs: Arc) -> usize { + let mut count = 0; + + for res in &*rs { + match res { + Ok(_) => { + count += 1; + } + Err(err) => panic!(format!("{:?}", err)), + } + } + + count +} diff --git a/tests/src/exp_bitwise.rs b/tests/src/exp_bitwise.rs new file mode 100644 index 00000000..737275ae --- /dev/null +++ b/tests/src/exp_bitwise.rs @@ -0,0 +1,408 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +use crate::common; +use env_logger; + +use aerospike::expressions::*; +use aerospike::expressions::bitwise::*; +use aerospike::operations::bitwise::{BitPolicy, BitwiseOverflowActions, BitwiseResizeFlags}; +use aerospike::*; +use std::sync::Arc; + +const EXPECTED: usize = 100; + +fn create_test_set(no_records: usize) -> String { + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..no_records as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", as_blob!(vec![0b00000001, 0b01000010])); + let bins = vec![&ibin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + } + + set_name +} + +#[test] +fn expression_bitwise() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // EQ + let rs = test_filter( + eq( + count( + int_val(0), + int_val(16), + blob_bin("bin".to_string()), + ), + int_val(3), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "COUNT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(16), + resize( + &BitPolicy::default(), + int_val(4), + BitwiseResizeFlags::Default, + blob_bin("bin".to_string()), + ), + ), + int_val(3), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "RESIZE Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(16), + insert( + &BitPolicy::default(), + int_val(0), + blob_val(vec![0b11111111]), + blob_bin("bin".to_string()), + ), + ), + int_val(9), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "INSERT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + remove( + &BitPolicy::default(), + int_val(0), + int_val(1), + blob_bin("bin".to_string()), + ), + ), + int_val(2), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "REMOVE Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + set( + &BitPolicy::default(), + int_val(0), + int_val(8), + blob_val(vec![0b10101010]), + blob_bin("bin".to_string()), + ), + ), + int_val(4), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "SET Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + bitwise::or( + &BitPolicy::default(), + int_val(0), + int_val(8), + blob_val(vec![0b10101010]), + blob_bin("bin".to_string()), + ), + ), + int_val(5), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "OR Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + xor( + &BitPolicy::default(), + int_val(0), + int_val(8), + blob_val(vec![0b10101011]), + blob_bin("bin".to_string()), + ), + ), + int_val(4), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "XOR Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + bitwise::and( + &BitPolicy::default(), + int_val(0), + int_val(8), + blob_val(vec![0b10101011]), + blob_bin("bin".to_string()), + ), + ), + int_val(1), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "AND Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + bitwise::not( + &BitPolicy::default(), + int_val(0), + int_val(8), + blob_bin("bin".to_string()), + ), + ), + int_val(7), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "NOT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + lshift( + &BitPolicy::default(), + int_val(0), + int_val(16), + int_val(9), + blob_bin("bin".to_string()), + ), + ), + int_val(2), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "LSHIFT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + rshift( + &BitPolicy::default(), + int_val(0), + int_val(8), + int_val(3), + blob_bin("bin".to_string()), + ), + ), + int_val(0), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "RSHIFT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + add( + &BitPolicy::default(), + int_val(0), + int_val(8), + int_val(128), + false, + BitwiseOverflowActions::Wrap, + blob_bin("bin".to_string()), + ), + ), + int_val(2), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "ADD Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + subtract( + &BitPolicy::default(), + int_val(0), + int_val(8), + int_val(1), + false, + BitwiseOverflowActions::Wrap, + blob_bin("bin".to_string()), + ), + ), + int_val(0), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "SUBTRACT Test Failed"); + + let rs = test_filter( + eq( + count( + int_val(0), + int_val(8), + set_int( + &BitPolicy::default(), + int_val(0), + int_val(8), + int_val(255), + blob_bin("bin".to_string()), + ), + ), + int_val(8), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "SET INT Test Failed"); + + let rs = test_filter( + eq( + get( + int_val(0), + int_val(8), + blob_bin("bin".to_string()), + ), + blob_val(vec![0b00000001]), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "GET Test Failed"); + + let rs = test_filter( + eq( + lscan( + int_val(8), + int_val(8), + bool_val(true), + blob_bin("bin".to_string()), + ), + int_val(1), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "LSCAN Test Failed"); + + let rs = test_filter( + eq( + rscan( + int_val(8), + int_val(8), + bool_val(true), + blob_bin("bin".to_string()), + ), + int_val(6), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "RSCAN Test Failed"); + + let rs = test_filter( + eq( + get_int( + int_val(0), + int_val(8), + false, + blob_bin("bin".to_string()), + ), + int_val(1), + ), + &set_name, + ); + let item_count = count_results(rs); + assert_eq!(item_count, 100, "RSCAN Test Failed"); +} + +fn test_filter(filter: FilterExpression, set_name: &str) -> Arc { + let client = common::client(); + let namespace = common::namespace(); + + let mut qpolicy = QueryPolicy::default(); + qpolicy.filter_expression = Some(filter); + + let statement = Statement::new(namespace, set_name, Bins::All); + client.query(&qpolicy, statement).unwrap() +} + +fn count_results(rs: Arc) -> usize { + let mut count = 0; + + for res in &*rs { + match res { + Ok(_) => { + count += 1; + } + Err(err) => panic!(format!("{:?}", err)), + } + } + + count +} diff --git a/tests/src/exp_hll.rs b/tests/src/exp_hll.rs new file mode 100644 index 00000000..36c3da36 --- /dev/null +++ b/tests/src/exp_hll.rs @@ -0,0 +1,194 @@ +// Copyright 2015-2020 Aerospike, Inc. +// +// Portions may be licensed to Aerospike, Inc. under one or more contributor +// license agreements. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +use crate::common; +use env_logger; + +use aerospike::expressions::*; +use aerospike::expressions::hll::*; +use aerospike::expressions::lists::*; +use aerospike::operations::hll::HLLPolicy; +use aerospike::operations::lists::ListReturnType; +use aerospike::*; +use std::sync::Arc; + +const EXPECTED: usize = 100; + +fn create_test_set(no_records: usize) -> String { + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..no_records as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", i); + let lbin = as_bin!("lbin", as_list!(i, "a")); + let bins = vec![&ibin, &lbin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + let data = vec![Value::from("asd"), Value::from(i)]; + let data2 = vec![Value::from("asd"), Value::from(i), Value::from(i + 1)]; + let ops = [ + operations::hll::add_with_index_and_min_hash( + &HLLPolicy::default(), + "hllbin", + &data, + 8, + 0, + ), + operations::hll::add_with_index_and_min_hash( + &HLLPolicy::default(), + "hllbin2", + &data2, + 8, + 0, + ), + ]; + client.operate(&WritePolicy::default(), &key, &ops).unwrap(); + } + + set_name +} + +#[test] +fn expression_hll() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + let rs = test_filter( + eq( + get_count(add_with_index_and_min_hash( + HLLPolicy::default(), + list_val(vec![Value::from(48715414)]), + int_val(8), + int_val(0), + hll_bin("hllbin".to_string()), + )), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "HLL INIT Test Failed"); + + let rs = test_filter( + eq( + may_contain( + list_val(vec![Value::from(55)]), + hll_bin("hllbin".to_string()), + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "HLL MAY CONTAIN Test Failed"); + + let rs = test_filter( + lt( + get_by_index( + ListReturnType::Values, + ExpType::INT, + int_val(0), + describe(hll_bin("hllbin".to_string())), + &[], + ), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "HLL DESCRIBE Test Failed"); + + let rs = test_filter( + eq( + get_count(get_union( + hll_bin("hllbin".to_string()), + hll_bin("hllbin2".to_string()), + )), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 97, "HLL GET UNION Test Failed"); + + let rs = test_filter( + eq( + get_union_count( + hll_bin("hllbin".to_string()), + hll_bin("hllbin2".to_string()), + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 97, "HLL GET UNION COUNT Test Failed"); + + let rs = test_filter( + eq( + get_intersect_count( + hll_bin("hllbin".to_string()), + hll_bin("hllbin2".to_string()), + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "HLL GET INTERSECT COUNT Test Failed"); + + let rs = test_filter( + gt( + get_similarity( + hll_bin("hllbin".to_string()), + hll_bin("hllbin2".to_string()), + ), + float_val(0.5f64), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "HLL GET INTERSECT COUNT Test Failed"); +} + +fn test_filter(filter: FilterExpression, set_name: &str) -> Arc { + let client = common::client(); + let namespace = common::namespace(); + + let mut qpolicy = QueryPolicy::default(); + qpolicy.filter_expression = Some(filter); + + let statement = Statement::new(namespace, set_name, Bins::All); + client.query(&qpolicy, statement).unwrap() +} + +fn count_results(rs: Arc) -> usize { + let mut count = 0; + + for res in &*rs { + match res { + Ok(_) => { + count += 1; + } + Err(err) => panic!(format!("{:?}", err)), + } + } + + count +} diff --git a/tests/src/exp_list.rs b/tests/src/exp_list.rs new file mode 100644 index 00000000..f9400da5 --- /dev/null +++ b/tests/src/exp_list.rs @@ -0,0 +1,550 @@ +use crate::common; +use env_logger; + +use aerospike::expressions::*; +use aerospike::expressions::lists::*; +use aerospike::operations::lists::{ListPolicy, ListReturnType}; +use aerospike::*; +use std::sync::Arc; + +const EXPECTED: usize = 100; + +fn create_test_set(no_records: usize) -> String { + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..no_records as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", as_list!(1, 2, 3, i)); + let bins = vec![&ibin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + } + + set_name +} + +#[test] +fn expression_list() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + // EQ + let rs = test_filter( + eq( + size( + append( + ListPolicy::default(), + int_val(999), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(5), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "SIZE AND APPEND Test Failed"); + + let rs = test_filter( + eq( + size( + append_items( + ListPolicy::default(), + list_val(vec![Value::from(555), Value::from("asd")]), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(6), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "SIZE AND APPEND ITEMS Test Failed"); + + let rs = test_filter( + eq( + size( + clear(list_bin("bin".to_string()), &[]), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "CLEAR Test Failed"); + + let rs = test_filter( + eq( + get_by_value( + ListReturnType::Count, + int_val(234), + insert( + ListPolicy::default(), + int_val(1), + int_val(234), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY VALUE AND INSERT Test Failed"); + + let rs = test_filter( + eq( + get_by_value_list( + ListReturnType::Count, + list_val(vec![Value::from(51), Value::from(52)]), + list_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 2, "GET BY VALUE LIST Test Failed"); + + let rs = test_filter( + eq( + size( + insert_items( + ListPolicy::default(), + int_val(4), + list_val(vec![Value::from(222), Value::from(223)]), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(6), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "INSERT LIST Test Failed"); + + let rs = test_filter( + eq( + get_by_index( + ListReturnType::Values, + ExpType::INT, + int_val(3), + increment( + ListPolicy::default(), + int_val(3), + int_val(100), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(102), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY INDEX AND INCREMENT Test Failed"); + + let rs = test_filter( + eq( + get_by_index( + ListReturnType::Values, + ExpType::INT, + int_val(3), + set( + ListPolicy::default(), + int_val(3), + int_val(100), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(100), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY INDEX AND SET Test Failed"); + + let rs = test_filter( + eq( + get_by_index_range_count( + ListReturnType::Values, + int_val(2), + int_val(2), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(3), Value::from(15)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_index_range( + ListReturnType::Values, + int_val(2), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(3), Value::from(15)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_rank( + ListReturnType::Values, + ExpType::INT, + int_val(3), + list_bin("bin".to_string()), + &[], + ), + int_val(25), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY RANK Test Failed"); + + let rs = test_filter( + eq( + get_by_rank_range( + ListReturnType::Values, + int_val(2), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(3), Value::from(25)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_rank_range_count( + ListReturnType::Values, + int_val(2), + int_val(2), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(3), Value::from(3)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_value_range( + ListReturnType::Values, + Some(int_val(1)), + Some(int_val(3)), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(1), Value::from(2)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "GET BY VALUE RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range( + ListReturnType::Count, + int_val(2), + int_val(0), + list_bin("bin".to_string()), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "GET BY VAL REL RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range_count( + ListReturnType::Values, + int_val(2), + int_val(1), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(3)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "GET BY VAL REL RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value( + int_val(3), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "REMOVE BY VALUE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_list( + list_val(vec![Value::from(1), Value::from(2)]), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "REMOVE BY VALUE LIST Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_range( + Some(int_val(1)), + Some(int_val(3)), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 98, "REMOVE BY VALUE RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_relative_rank_range( + int_val(3), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 97, "REMOVE BY VALUE REL RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_relative_rank_range_count( + int_val(2), + int_val(1), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!( + count, 100, + "REMOVE BY VALUE REL RANK RANGE LIST Test Failed" + ); + + let rs = test_filter( + eq( + size( + remove_by_index( + int_val(0), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index_range( + int_val(2), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index_range_count( + int_val(2), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index_range_count( + int_val(2), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank( + int_val(2), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank_range( + int_val(2), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank_range_count( + int_val(2), + int_val(1), + list_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(3), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK RANGE COUNT Test Failed"); +} + +fn test_filter(filter: FilterExpression, set_name: &str) -> Arc { + let client = common::client(); + let namespace = common::namespace(); + + let mut qpolicy = QueryPolicy::default(); + qpolicy.filter_expression = Some(filter); + + let statement = Statement::new(namespace, set_name, Bins::All); + client.query(&qpolicy, statement).unwrap() +} + +fn count_results(rs: Arc) -> usize { + let mut count = 0; + + for res in &*rs { + match res { + Ok(_) => { + count += 1; + } + Err(err) => panic!(format!("{:?}", err)), + } + } + + count +} diff --git a/tests/src/exp_map.rs b/tests/src/exp_map.rs new file mode 100644 index 00000000..fdb20259 --- /dev/null +++ b/tests/src/exp_map.rs @@ -0,0 +1,645 @@ +use crate::common; +use env_logger; + +use aerospike::expressions::*; +use aerospike::expressions::maps::*; +use aerospike::*; +use std::collections::HashMap; +use std::sync::Arc; + +const EXPECTED: usize = 100; + +fn create_test_set(no_records: usize) -> String { + let client = common::client(); + let namespace = common::namespace(); + let set_name = common::rand_str(10); + + let wpolicy = WritePolicy::default(); + for i in 0..no_records as i64 { + let key = as_key!(namespace, &set_name, i); + let ibin = as_bin!("bin", as_map!("test" => i , "test2" => "a")); + let bins = vec![&ibin]; + client.delete(&wpolicy, &key).unwrap(); + client.put(&wpolicy, &key, &bins).unwrap(); + } + + set_name +} + +#[test] +fn expression_map() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + let rs = test_filter( + eq( + get_by_key( + MapReturnType::Value, + ExpType::INT, + string_val("test3".to_string()), + put( + &MapPolicy::default(), + string_val("test3".to_string()), + int_val(999), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(999), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY KEY AND APPEND Test Failed"); + + let mut map: HashMap = HashMap::new(); + map.insert(Value::from("test4"), Value::from(333)); + map.insert(Value::from("test5"), Value::from(444)); + let rs = test_filter( + eq( + get_by_key_list( + MapReturnType::Value, + list_val(vec![Value::from("test4"), Value::from("test5")]), + put_items( + &MapPolicy::default(), + map_val(map), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + list_val(vec![Value::from(333), Value::from(444)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY KEY LIST AND APPEND LIST Test Failed"); + + let rs = test_filter( + eq( + get_by_value( + MapReturnType::Count, + int_val(5), + increment( + &MapPolicy::default(), + string_val("test".to_string()), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY VALUE AND INCREMENT Test Failed"); + + let rs = test_filter( + eq( + size( + clear(map_bin("bin".to_string()), &[]), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "SIZE AND CLEAR Test Failed"); + + let rs = test_filter( + eq( + get_by_value_list( + MapReturnType::Count, + list_val(vec![Value::from(1), Value::from("a")]), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY VALUE LIST Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range( + MapReturnType::Count, + int_val(1), + int_val(0), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "GET BY VALUE REL RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range_count( + MapReturnType::Count, + int_val(1), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY VALUE REL RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range_count( + MapReturnType::Count, + int_val(1), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY VALUE REL RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range_count( + MapReturnType::Count, + int_val(1), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY VALUE REL RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_value_relative_rank_range_count( + MapReturnType::Count, + int_val(1), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY VALUE REL RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_index( + MapReturnType::Value, + ExpType::INT, + int_val(0), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY INDEX Test Failed"); + + let rs = test_filter( + eq( + get_by_index_range( + MapReturnType::Count, + int_val(0), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_index_range_count( + MapReturnType::Value, + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(2)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_rank( + MapReturnType::Value, + ExpType::INT, + int_val(0), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY RANK Test Failed"); + + let rs = test_filter( + eq( + get_by_rank_range( + MapReturnType::Value, + int_val(1), + map_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from("a")]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_rank_range_count( + MapReturnType::Value, + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + list_val(vec![Value::from(15)]), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "GET BY RANK RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + get_by_value_range( + MapReturnType::Count, + Some(int_val(0)), + Some(int_val(18)), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 18, "GET BY VALUE RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_key_range( + MapReturnType::Count, + None, + Some(string_val("test25".to_string())), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY KEY RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_key_relative_index_range( + MapReturnType::Count, + string_val("test".to_string()), + int_val(0), + map_bin("bin".to_string()), + &[], + ), + int_val(2), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY KEY REL INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + get_by_key_relative_index_range_count( + MapReturnType::Count, + string_val("test".to_string()), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "GET BY KEY REL INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_key( + string_val("test".to_string()), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY KEY Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_key_list( + list_val(vec![Value::from("test"), Value::from("test2")]), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY KEY LIST Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_key_range( + Some(string_val("test".to_string())), + None, + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY KEY RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_key_relative_index_range( + string_val("test".to_string()), + int_val(0), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY KEY REL INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_key_relative_index_range_count( + string_val("test".to_string()), + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!( + count, 100, + "REMOVE BY KEY REL INDEX RANGE COUNT Test Failed" + ); + + let rs = test_filter( + eq( + size( + remove_by_value( + int_val(5), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "REMOVE BY VALUE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_list( + list_val(vec![Value::from("a"), Value::from(15)]), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "REMOVE BY VALUE LIST Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_value_range( + Some(int_val(5)), + Some(int_val(15)), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 10, "REMOVE BY VALUE RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index( + int_val(0), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index_range( + int_val(0), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_index_range_count( + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY INDEX RANGE COUNT Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank( + int_val(0), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank_range( + int_val(0), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK RANGE Test Failed"); + + let rs = test_filter( + eq( + size( + remove_by_rank_range_count( + int_val(0), + int_val(1), + map_bin("bin".to_string()), + &[], + ), + &[], + ), + int_val(1), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 100, "REMOVE BY RANK RANGE COUNT Test Failed"); +} + +fn test_filter(filter: FilterExpression, set_name: &str) -> Arc { + let client = common::client(); + let namespace = common::namespace(); + + let mut qpolicy = QueryPolicy::default(); + qpolicy.filter_expression = Some(filter); + + let statement = Statement::new(namespace, set_name, Bins::All); + client.query(&qpolicy, statement).unwrap() +} + +fn count_results(rs: Arc) -> usize { + let mut count = 0; + + for res in &*rs { + match res { + Ok(_) => { + count += 1; + } + Err(err) => panic!(format!("{:?}", err)), + } + } + + count +} diff --git a/tests/src/hll.rs b/tests/src/hll.rs new file mode 100644 index 00000000..96d453b5 --- /dev/null +++ b/tests/src/hll.rs @@ -0,0 +1,117 @@ +use crate::common; +use env_logger; + +use aerospike::operations::hll; +use aerospike::operations::hll::HLLPolicy; +use aerospike::{as_key, as_list, as_val, Bins, FloatValue, ReadPolicy, Value, WritePolicy}; + +#[test] +fn hll() { + let _ = env_logger::try_init(); + + let client = common::client(); + let namespace = common::namespace(); + let set_name = &common::rand_str(10); + + let key = as_key!(namespace, set_name, "test"); + + let hpolicy = HLLPolicy::default(); + let wpolicy = WritePolicy::default(); + let rpolicy = ReadPolicy::default(); + + let ops = &vec![hll::init(&hpolicy, "bin", 4)]; + client.operate(&wpolicy, &key, ops).unwrap(); + + let v = vec![Value::from("asd123")]; + let ops = &vec![hll::add(&hpolicy, "bin", &v)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::Int(1), + "Register update did not match" + ); + + let ops = &vec![hll::get_count("bin")]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::Int(1), + "HLL Count did not match" + ); + + let ops = &vec![hll::init_with_min_hash(&hpolicy, "bin2", 8, 0)]; + client.operate(&wpolicy, &key, ops).unwrap(); + + let ops = &vec![hll::fold("bin2", 6)]; + client.operate(&wpolicy, &key, ops).unwrap(); + + let v2 = vec![Value::from("123asd")]; + let ops = &vec![hll::add(&hpolicy, "bin2", &v2)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin2").unwrap(), + Value::Int(1), + "Register update did not match" + ); + + let ops = &vec![hll::describe("bin")]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + as_list!(4, 0), + "Index bits did not match" + ); + + let rec = client.get(&rpolicy, &key, Bins::from(["bin2"])).unwrap(); + let bin2val = vec![rec.bins.get("bin2").unwrap().clone()]; + + let ops = &vec![hll::get_intersect_count("bin", &bin2val)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::from(0), + "Intersect Count is wrong" + ); + + let ops = &vec![hll::get_union_count("bin", &bin2val)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::from(2), + "Union Count is wrong" + ); + + let ops = &vec![hll::get_union("bin", &bin2val)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + let val = Value::HLL(vec![ + 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + assert_eq!(*rec.bins.get("bin").unwrap(), val, "Union does not match"); + + let ops = &vec![hll::refresh_count("bin")]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::Int(1), + "HLL Refresh Count did not match" + ); + + let ops = &vec![ + hll::set_union(&hpolicy, "bin", &bin2val), + hll::get_count("bin"), + ]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::from(2), + "Written Union count does not match" + ); + + let ops = &vec![hll::get_similarity("bin", &bin2val)]; + let rec = client.operate(&wpolicy, &key, ops).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + Value::Float(FloatValue::F64(4602678819172646912)), + "Similarity failed" + ); +} diff --git a/tests/src/mod.rs b/tests/src/mod.rs index f3b8e1f8..d48b3813 100644 --- a/tests/src/mod.rs +++ b/tests/src/mod.rs @@ -17,6 +17,12 @@ mod batch; mod cdt_bitwise; mod cdt_list; mod cdt_map; +mod exp; +mod exp_bitwise; +mod exp_hll; +mod exp_list; +mod exp_map; +mod hll; mod index; mod kv; mod query; diff --git a/tests/src/query.rs b/tests/src/query.rs index 74d3e7c3..afb22070 100644 --- a/tests/src/query.rs +++ b/tests/src/query.rs @@ -98,33 +98,6 @@ fn query_single_consumer() { assert_eq!(count, 10); } -#[test] -fn query_predexp() { - let _ = env_logger::try_init(); - - let client = common::client(); - let namespace = common::namespace(); - let set_name = create_test_set(EXPECTED); - let qpolicy = QueryPolicy::default(); - - // Filter Query - let mut statement = Statement::new(namespace, &set_name, Bins::All); - statement.add_predicate(as_predexp_integer_bin!("bin".to_string())); - statement.add_predicate(as_predexp_integer_value!(19)); - statement.add_predicate(as_predexp_integer_lesseq!()); - let rs = client.query(&qpolicy, statement).unwrap(); - let mut count = 0; - for res in &*rs { - match res { - Ok(_rec) => { - count += 1; - } - Err(err) => panic!(format!("{:?}", err)), - } - } - assert_eq!(count, 20); -} - #[test] fn query_nobins() { let _ = env_logger::try_init();