diff --git a/src/commands/buffer.rs b/src/commands/buffer.rs index 38fd3055..c2fa4f77 100644 --- a/src/commands/buffer.rs +++ b/src/commands/buffer.rs @@ -507,13 +507,20 @@ impl Buffer { | Operation { op: OperationType::HllRead, .. + } + | Operation { + op: OperationType::ExpRead, + .. } => read_attr |= INFO1_READ, _ => write_attr |= INFO2_WRITE, } let each_op = matches!( operation.data, - OperationData::CdtMapOp(_) | OperationData::CdtBitOp(_) + OperationData::CdtMapOp(_) + | OperationData::CdtBitOp(_) + | OperationData::HLLOp(_) + | OperationData::EXPOp(_) ); if policy.respond_per_each_op || each_op { @@ -556,7 +563,6 @@ impl Buffer { for operation in operations { operation.write_to(self)?; } - self.end() } diff --git a/src/expressions/hll.rs b/src/expressions/hll.rs index 065ffded..4ea5f8a0 100644 --- a/src/expressions/hll.rs +++ b/src/expressions/hll.rs @@ -23,6 +23,7 @@ const MODULE: i64 = 2; #[doc(hidden)] pub enum HllExpOp { + Init = 0, Add = 1, Count = 50, Union = 51, @@ -33,6 +34,33 @@ pub enum HllExpOp { MayContain = 56, } +/// Create expression that creates a new HLL or resets an existing HLL. +pub fn init( + policy: HLLPolicy, + index_bit_count: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + init_with_min_hash(policy, index_bit_count, int_val(-1), bin) +} + +/// Create expression that creates a new HLL or resets an existing HLL with minhash bits. +pub fn init_with_min_hash( + policy: HLLPolicy, + index_bit_count: FilterExpression, + min_hash_count: FilterExpression, + bin: FilterExpression, +) -> FilterExpression { + add_write( + bin, + vec![ + ExpressionArgument::Value(Value::from(HllExpOp::Init as i64)), + ExpressionArgument::FilterExpression(index_bit_count), + ExpressionArgument::FilterExpression(min_hash_count), + ExpressionArgument::Value(Value::from(policy.flags as i64)), + ], + ) +} + /// Create expression that adds list values to a HLL set and returns HLL set. /// The function assumes HLL bin already exists. /// ``` diff --git a/src/expressions/mod.rs b/src/expressions/mod.rs index 1bdd4135..b8f0fca6 100644 --- a/src/expressions/mod.rs +++ b/src/expressions/mod.rs @@ -20,7 +20,6 @@ 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}; @@ -57,6 +56,7 @@ pub enum ExpType { #[derive(Debug, Clone, Copy)] #[doc(hidden)] pub enum ExpOp { + Unknown = 0, EQ = 1, NE = 2, GT = 3, @@ -68,6 +68,31 @@ pub enum ExpOp { And = 16, Or = 17, Not = 18, + Xor = 19, + Add = 20, + Sub = 21, + Mul = 22, + Div = 23, + Pow = 24, + Log = 25, + Mod = 26, + Abs = 27, + Floor = 28, + Ceil = 29, + ToInt = 30, + ToFloat = 31, + IntAnd = 32, + IntOr = 33, + IntXor = 34, + IntNot = 35, + IntLshift = 36, + IntRshift = 37, + IntARshift = 38, + IntCount = 39, + IntLscan = 40, + IntRscan = 41, + Min = 50, + Max = 51, DigestModulo = 64, DeviceSize = 65, LastUpdate = 66, @@ -80,6 +105,9 @@ pub enum ExpOp { Key = 80, Bin = 81, BinType = 82, + Cond = 123, + Var = 124, + Let = 125, Quoted = 126, Call = 127, } @@ -112,7 +140,7 @@ pub struct FilterExpression { module: Option, /// Sub commands for the CmdExp operation exps: Option>, - + /// Optional Arguments (CDT) arguments: Option>, } @@ -144,10 +172,26 @@ impl 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)?; + if let Some(val) = &self.val { + // DEF expression + size += pack_raw_string(buf, &val.to_string())?; + size += exps[0].pack(buf)?; + } else { + // Normal Expressions + match self.cmd.unwrap() { + ExpOp::Let => { + // Let wire format: LET , , , , ..., + let count = (exps.len() - 1) * 2 + 2; + size += pack_array_begin(buf, count)?; + } + _ => { + 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) } @@ -186,13 +230,13 @@ impl FilterExpression { | 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)?; + size += pack_array_begin(buf, 3)?; + size += pack_integer(buf, 0xff)?; + size += 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_integer(buf, i64::from(c.id))?; + size += pack_value(buf, &c.value)?; } } } @@ -228,10 +272,10 @@ impl FilterExpression { // 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 + ExpOp::BinType | ExpOp::Var => { + // BinType/Var encoder size += pack_array_begin(buf, 2)?; - // BinType Operation + // BinType/Var 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())?; @@ -828,4 +872,597 @@ pub fn le(left: FilterExpression, right: FilterExpression) -> FilterExpression { } } -// ---------------------------------------------- +/// Create "add" (+) operator that applies to a variable number of expressions. +/// Return sum of all `FilterExpressions` given. All arguments must resolve to the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// use aerospike::expressions::{eq, num_add, int_bin, int_val}; +/// // a + b + c == 10 +/// eq(num_add(vec![int_bin("a".to_string()), int_bin("b".to_string()), int_bin("c".to_string())]), int_val(10)); +/// ``` +pub const fn num_add(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Add), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create "subtract" (-) operator that applies to a variable number of expressions. +/// If only one `FilterExpressions` is provided, return the negation of that argument. +/// Otherwise, return the sum of the 2nd to Nth `FilterExpressions` subtracted from the 1st +/// `FilterExpressions`. All `FilterExpressions` must resolve to the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// use aerospike::expressions::{gt, num_sub, int_bin, int_val}; +/// // a - b - c > 10 +/// gt(num_sub(vec![int_bin("a".to_string()), int_bin("b".to_string()), int_bin("c".to_string())]), int_val(10)); +/// ``` +pub const fn num_sub(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Sub), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create "multiply" (*) operator that applies to a variable number of expressions. +/// Return the product of all `FilterExpressions`. If only one `FilterExpressions` is supplied, return +/// that `FilterExpressions`. All `FilterExpressions` must resolve to the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// use aerospike::expressions::{lt, num_mul, int_val, int_bin}; +/// // a * b * c < 100 +/// lt(num_mul(vec![int_bin("a".to_string()), int_bin("b".to_string()), int_bin("c".to_string())]), int_val(100)); +/// ``` +pub const fn num_mul(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Mul), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create "divide" (/) operator that applies to a variable number of expressions. +/// If there is only one `FilterExpressions`, returns the reciprocal for that `FilterExpressions`. +/// Otherwise, return the first `FilterExpressions` divided by the product of the rest. +/// All `FilterExpressions` must resolve to the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// use aerospike::expressions::{lt, int_val, int_bin, num_div}; +/// // a / b / c > 1 +/// lt(num_div(vec![int_bin("a".to_string()), int_bin("b".to_string()), int_bin("c".to_string())]), int_val(1)); +/// ``` +pub const fn num_div(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Div), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create "power" operator that raises a "base" to the "exponent" power. +/// All arguments must resolve to floats. +/// Requires server version 5.6.0+. +/// ``` +/// // pow(a, 2.0) == 4.0 +/// use aerospike::expressions::{eq, num_pow, float_bin, float_val}; +/// eq(num_pow(float_bin("a".to_string()), float_val(2.0)), float_val(4.0)); +/// ``` +pub fn num_pow(base: FilterExpression, exponent: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Pow), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![base, exponent]), + arguments: None, + } +} + +/// Create "log" operator for logarithm of "num" with base "base". +/// All arguments must resolve to floats. +/// Requires server version 5.6.0+. +/// ``` +/// // log(a, 2.0) == 4.0 +/// use aerospike::expressions::{eq, float_bin, float_val, num_log}; +/// eq(num_log(float_bin("a".to_string()), float_val(2.0)), float_val(4.0)); +/// ``` +pub fn num_log(num: FilterExpression, base: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Log), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![num, base]), + arguments: None, + } +} + +/// Create "modulo" (%) operator that determines the remainder of "numerator" +/// divided by "denominator". All arguments must resolve to integers. +/// Requires server version 5.6.0+. +/// ``` +/// // a % 10 == 0 +/// use aerospike::expressions::{eq, num_mod, int_val, int_bin}; +/// eq(num_mod(int_bin("a".to_string()), int_val(10)), int_val(0)); +/// ``` +pub fn num_mod(numerator: FilterExpression, denominator: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Mod), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![numerator, denominator]), + arguments: None, + } +} + +/// Create operator that returns absolute value of a number. +/// All arguments must resolve to integer or float. +/// Requires server version 5.6.0+. +/// ``` +/// // abs(a) == 1 +/// use aerospike::expressions::{eq, int_val, int_bin, num_abs}; +/// eq(num_abs(int_bin("a".to_string())), int_val(1)); +/// ``` +pub fn num_abs(value: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Abs), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value]), + arguments: None, + } +} + +/// Create expression that rounds a floating point number down to the closest integer value. +/// The return type is float. +// Requires server version 5.6.0+. +/// ``` +/// // floor(2.95) == 2.0 +/// use aerospike::expressions::{eq, num_floor, float_val}; +/// eq(num_floor(float_val(2.95)), float_val(2.0)); +/// ``` +pub fn num_floor(num: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Floor), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![num]), + arguments: None, + } +} + +/// Create expression that rounds a floating point number up to the closest integer value. +/// The return type is float. +/// Requires server version 5.6.0+. +/// ``` +/// // ceil(2.15) == 3.0 +/// use aerospike::expressions::{float_val, num_ceil, ge}; +/// ge(num_ceil(float_val(2.15)), float_val(3.0)); +/// ``` +pub fn num_ceil(num: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Ceil), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![num]), + arguments: None, + } +} + +/// Create expression that converts an integer to a float. +/// Requires server version 5.6.0+. +/// ``` +/// // int(2.5) == 2 +/// use aerospike::expressions::{float_val, eq, to_int, int_val}; +/// eq(to_int(float_val(2.5)), int_val(2)); +/// ``` +pub fn to_int(num: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::ToInt), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![num]), + arguments: None, + } +} + +/// Create expression that converts a float to an integer. +/// Requires server version 5.6.0+. +/// ``` +/// // float(2) == 2.0 +/// use aerospike::expressions::{float_val, eq, to_float, int_val}; +/// eq(to_float(int_val(2)), float_val(2.0)); +/// ``` +pub fn to_float(num: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::ToFloat), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![num]), + arguments: None, + } +} + +/// Create integer "and" (&) operator that is applied to two or more integers. +/// All arguments must resolve to integers. +/// Requires server version 5.6.0+. +/// ``` +/// // a & 0xff == 0x11 +/// use aerospike::expressions::{eq, int_val, int_and, int_bin}; +/// eq(int_and(vec![int_bin("a".to_string()), int_val(0xff)]), int_val(0x11)); +/// ``` +pub const fn int_and(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntAnd), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create integer "xor" (^) operator that is applied to two or more integers. +/// All arguments must resolve to integers. +/// Requires server version 5.6.0+. +/// ``` +/// // a ^ b == 16 +/// use aerospike::expressions::{eq, int_val, int_xor, int_bin}; +/// eq(int_xor(vec![int_bin("a".to_string()), int_bin("b".to_string())]), int_val(16)); +/// ``` +pub const fn int_xor(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntXor), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create integer "not" (~) operator. +/// Requires server version 5.6.0+. +/// ``` +/// // ~a == 7 +/// use aerospike::expressions::{eq, int_val, int_not, int_bin}; +/// eq(int_not(int_bin("a".to_string())), int_val(7)); +/// ``` +pub fn int_not(exp: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntNot), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![exp]), + arguments: None, + } +} + +/// Create integer "left shift" (<<) operator. +/// Requires server version 5.6.0+. +/// ``` +/// // a << 8 > 0xff +/// use aerospike::expressions::{int_val, int_bin, gt, int_lshift}; +/// gt(int_lshift(int_bin("a".to_string()), int_val(8)), int_val(0xff)); +/// ``` +pub fn int_lshift(value: FilterExpression, shift: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntLshift), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value, shift]), + arguments: None, + } +} + +/// Create integer "logical right shift" (>>>) operator. +/// Requires server version 5.6.0+. +/// ``` +/// // a >> 8 > 0xff +/// use aerospike::expressions::{int_val, int_bin, gt, int_rshift}; +/// gt(int_rshift(int_bin("a".to_string()), int_val(8)), int_val(0xff)); +/// ``` +pub fn int_rshift(value: FilterExpression, shift: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntRshift), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value, shift]), + arguments: None, + } +} + +/// Create integer "arithmetic right shift" (>>) operator. +/// The sign bit is preserved and not shifted. +/// Requires server version 5.6.0+. +/// ``` +/// // a >>> 8 > 0xff +/// use aerospike::expressions::{int_val, int_bin, gt, int_arshift}; +/// gt(int_arshift(int_bin("a".to_string()), int_val(8)), int_val(0xff)); +/// ``` +pub fn int_arshift(value: FilterExpression, shift: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntARshift), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value, shift]), + arguments: None, + } +} + +/// Create expression that returns count of integer bits that are set to 1. +/// Requires server version 5.6.0+. +/// ``` +/// // count(a) == 4 +/// use aerospike::expressions::{int_val, int_bin, int_count, eq}; +/// eq(int_count(int_bin("a".to_string())), int_val(4)); +/// ``` +pub fn int_count(exp: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntCount), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![exp]), + arguments: None, + } +} + +/// Create expression that scans integer bits from left (most significant bit) to +/// right (least significant bit), looking for a search bit value. When the +/// search value is found, the index of that bit (where the most significant bit is +/// index 0) is returned. If "search" is true, the scan will search for the bit +/// value 1. If "search" is false it will search for bit value 0. +/// Requires server version 5.6.0+. +/// ``` +/// // lscan(a, true) == 4 +/// use aerospike::expressions::{int_val, int_bin, eq, int_lscan, bool_val}; +/// eq(int_lscan(int_bin("a".to_string()), bool_val(true)), int_val(4)); +/// ``` +pub fn int_lscan(value: FilterExpression, search: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntLscan), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value, search]), + arguments: None, + } +} + +/// Create expression that scans integer bits from right (least significant bit) to +/// left (most significant bit), looking for a search bit value. When the +/// search value is found, the index of that bit (where the most significant bit is +/// index 0) is returned. If "search" is true, the scan will search for the bit +/// value 1. If "search" is false it will search for bit value 0. +/// Requires server version 5.6.0+. +/// ``` +/// // rscan(a, true) == 4 +/// use aerospike::expressions::{int_val, int_bin, eq, int_rscan, bool_val}; +/// eq(int_rscan(int_bin("a".to_string()), bool_val(true)), int_val(4)); +/// ``` +pub fn int_rscan(value: FilterExpression, search: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::IntRscan), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(vec![value, search]), + arguments: None, + } +} + +/// Create expression that returns the minimum value in a variable number of expressions. +/// All arguments must be the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// // min(a, b, c) > 0 +/// use aerospike::expressions::{int_val, int_bin, gt, min}; +/// gt(min(vec![int_bin("a".to_string()),int_bin("b".to_string()),int_bin("c".to_string())]), int_val(0)); +/// ``` +pub const fn min(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Min), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Create expression that returns the maximum value in a variable number of expressions. +/// All arguments must be the same type (integer or float). +/// Requires server version 5.6.0+. +/// ``` +/// // max(a, b, c) > 100 +/// use aerospike::expressions::{int_val, int_bin, gt, max}; +/// gt(max(vec![int_bin("a".to_string()),int_bin("b".to_string()),int_bin("c".to_string())]), int_val(100)); +/// ``` +pub const fn max(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Max), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +//-------------------------------------------------- +// Variables +//-------------------------------------------------- + +/// Conditionally select an expression from a variable number of expression pairs +/// followed by default expression action. +/// Requires server version 5.6.0+. +/// ``` +/// // Args Format: bool exp1, action exp1, bool exp2, action exp2, ..., action-default +/// // Apply operator based on type. +/// +/// use aerospike::expressions::{cond, int_bin, eq, int_val, num_add, num_sub, num_mul}; +/// cond( +/// vec![ +/// eq(int_bin("type".to_string()), int_val(0)), num_add(vec![int_bin("val1".to_string()), int_bin("val2".to_string())]), +/// eq(int_bin("type".to_string()), int_val(1)), num_sub(vec![int_bin("val1".to_string()), int_bin("val2".to_string())]), +/// eq(int_bin("type".to_string()), int_val(2)), num_mul(vec![int_bin("val1".to_string()), int_bin("val2".to_string())]), +/// int_val(-1) +/// ] +/// ); +/// ``` +pub const fn cond(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Cond), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Define variables and expressions in scope. +/// Requires server version 5.6.0+. +/// ``` +/// // 5 < a < 10 +/// use aerospike::expressions::{exp_let, def, int_bin, and, lt, int_val, var}; +/// exp_let( +/// vec![ +/// def("x".to_string(), int_bin("a".to_string())), +/// and(vec![ +/// lt(int_val(5), var("x".to_string()),), +/// lt(var("x".to_string()), int_val(10)) +/// ]) +/// ] +/// ); +/// ``` +pub const fn exp_let(exps: Vec) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Let), + val: None, + bin: None, + flags: None, + module: None, + exps: Some(exps), + arguments: None, + } +} + +/// Assign variable to an expression that can be accessed later. +/// Requires server version 5.6.0+. +/// ``` +/// // 5 < a < 10 +/// use aerospike::expressions::{exp_let, def, int_bin, and, lt, int_val, var}; +/// exp_let( +/// vec![ +/// def("x".to_string(), int_bin("a".to_string())), +/// and(vec![ +/// lt(int_val(5), var("x".to_string()),), +/// lt(var("x".to_string()), int_val(10)) +/// ]) +/// ] +/// ); +/// ``` +pub fn def(name: String, value: FilterExpression) -> FilterExpression { + FilterExpression { + cmd: None, + val: Some(Value::from(name)), + bin: None, + flags: None, + module: None, + exps: Some(vec![value]), + arguments: None, + } +} + +/// Retrieve expression value from a variable. +/// Requires server version 5.6.0+. +pub fn var(name: String) -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Var), + val: Some(Value::from(name)), + bin: None, + flags: None, + module: None, + exps: None, + arguments: None, + } +} + +/// Create unknown value. Used to intentionally fail an expression. +/// The failure can be ignored with `ExpWriteFlags` `EVAL_NO_FAIL` +/// or `ExpReadFlags` `EVAL_NO_FAIL`. +/// Requires server version 5.6.0+. +/// +/// ``` +/// // double v = balance - 100.0; +/// // return (v > 0.0)? v : unknown; +/// use aerospike::expressions::{exp_let, def, num_sub, float_bin, float_val, cond, ge, var, unknown}; +/// exp_let( +/// vec![ +/// def("v".to_string(), num_sub(vec![float_bin("balance".to_string()), float_val(100.0)])), +/// cond(vec![ge(var("v".to_string()), float_val(0.0)), var("v".to_string())]), +/// unknown() +/// ] +/// ); +/// ``` +pub const fn unknown() -> FilterExpression { + FilterExpression { + cmd: Some(ExpOp::Unknown), + val: None, + bin: None, + flags: None, + module: None, + exps: None, + arguments: None, + } +} diff --git a/src/operations/exp.rs b/src/operations/exp.rs new file mode 100644 index 00000000..114a6618 --- /dev/null +++ b/src/operations/exp.rs @@ -0,0 +1,134 @@ +// 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. + +//! Expression Operations. +//! This functions allow users to run `FilterExpressions` as Operate commands. + +use crate::commands::buffer::Buffer; +use crate::errors::Result; +use crate::expressions::FilterExpression; +use crate::msgpack::encoder::{pack_array_begin, pack_integer}; +use crate::operations::{Operation, OperationBin, OperationData, OperationType}; +use crate::ParticleType; + +/// Expression write Flags +pub enum ExpWriteFlags { + /// Default. Allow create or update. + Default = 0, + /// If bin does not exist, a new bin will be created. + /// If bin exists, the operation will be denied. + /// If bin exists, fail with Bin Exists + CreateOnly = 1 << 0, + /// If bin exists, the bin will be overwritten. + /// If bin does not exist, the operation will be denied. + /// If bin does not exist, fail with Bin Not Found + UpdateOnly = 1 << 1, + /// If expression results in nil value, then delete the bin. + /// Otherwise, return OP Not Applicable when NoFail is not set + AllowDelete = 1 << 2, + /// Do not raise error if operation is denied. + PolicyNoFail = 1 << 3, + /// Ignore failures caused by the expression resolving to unknown or a non-bin type. + EvalNoFail = 1 << 4, +} + +#[doc(hidden)] +pub type ExpressionEncoder = Box, &ExpOperation) -> Result>; + +#[doc(hidden)] +pub struct ExpOperation<'a> { + pub encoder: ExpressionEncoder, + pub policy: i64, + pub exp: &'a FilterExpression, +} + +impl<'a> ExpOperation<'a> { + #[doc(hidden)] + pub const fn particle_type(&self) -> ParticleType { + ParticleType::BLOB + } + #[doc(hidden)] + pub fn estimate_size(&self) -> Result { + let size: usize = (self.encoder)(&mut None, self)?; + Ok(size) + } + #[doc(hidden)] + pub fn write_to(&self, buffer: &mut Buffer) -> Result { + let size: usize = (self.encoder)(&mut Some(buffer), self)?; + Ok(size) + } +} + +/// Expression read Flags +pub enum ExpReadFlags { + /// Default + Default = 0, + /// Ignore failures caused by the expression resolving to unknown or a non-bin type. + EvalNoFail = 1 << 4, +} + +/// Create operation that performs a expression that writes to record bin. +pub fn write_exp<'a>( + bin: &'a str, + exp: &'a FilterExpression, + flags: ExpWriteFlags, +) -> Operation<'a> { + let op = ExpOperation { + encoder: Box::new(pack_write_exp), + policy: flags as i64, + exp, + }; + Operation { + op: OperationType::ExpWrite, + ctx: &[], + bin: OperationBin::Name(bin), + data: OperationData::EXPOp(op), + } +} + +/// Create operation that performs a read expression. +pub fn read_exp<'a>( + name: &'a str, + exp: &'a FilterExpression, + flags: ExpReadFlags, +) -> Operation<'a> { + let op = ExpOperation { + encoder: Box::new(pack_read_exp), + policy: flags as i64, + exp, + }; + Operation { + op: OperationType::ExpRead, + ctx: &[], + bin: OperationBin::Name(name), + data: OperationData::EXPOp(op), + } +} + +fn pack_write_exp(buf: &mut Option<&mut Buffer>, exp_op: &ExpOperation) -> Result { + let mut size = 0; + size += pack_array_begin(buf, 2)?; + size += exp_op.exp.pack(buf)?; + size += pack_integer(buf, exp_op.policy)?; + Ok(size) +} + +fn pack_read_exp(buf: &mut Option<&mut Buffer>, exp_op: &ExpOperation) -> Result { + let mut size = 0; + size += pack_array_begin(buf, 2)?; + size += exp_op.exp.pack(buf)?; + size += pack_integer(buf, exp_op.policy)?; + Ok(size) +} diff --git a/src/operations/mod.rs b/src/operations/mod.rs index 66802135..57e38a50 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 exp; pub mod hll; pub mod lists; pub mod maps; @@ -32,6 +33,7 @@ use crate::commands::buffer::Buffer; use crate::commands::ParticleType; use crate::errors::Result; use crate::operations::cdt_context::CdtContext; +use crate::operations::exp::ExpOperation; use crate::Value; #[derive(Clone, Copy)] @@ -42,6 +44,8 @@ pub enum OperationType { CdtRead = 3, CdtWrite = 4, Incr = 5, + ExpRead = 7, + ExpWrite = 8, Append = 9, Prepend = 10, Touch = 11, @@ -60,6 +64,7 @@ pub enum OperationData<'a> { CdtMapOp(CdtOperation<'a>), CdtBitOp(CdtOperation<'a>), HLLOp(CdtOperation<'a>), + EXPOp(ExpOperation<'a>), } #[doc(hidden)] @@ -99,6 +104,7 @@ impl<'a> Operation<'a> { size += match self.data { OperationData::None => 0, OperationData::Value(value) => value.estimate_size()?, + OperationData::EXPOp(ref exp_op) => exp_op.estimate_size()?, OperationData::CdtListOp(ref cdt_op) | OperationData::CdtMapOp(ref cdt_op) | OperationData::CdtBitOp(ref cdt_op) @@ -133,6 +139,10 @@ impl<'a> Operation<'a> { size += self.write_op_header_to(buffer, cdt_op.particle_type() as u8)?; size += cdt_op.write_to(buffer, self.ctx)?; } + OperationData::EXPOp(ref exp) => { + size += self.write_op_header_to(buffer, ParticleType::BLOB as u8)?; + size += exp.write_to(buffer)?; + } }; Ok(size) diff --git a/tests/src/exp.rs b/tests/src/exp.rs index dc0f7571..e1a1bace 100644 --- a/tests/src/exp.rs +++ b/tests/src/exp.rs @@ -163,6 +163,249 @@ fn expression_data_types() { assert_eq!(count, 100, "BIN TYPE Test Failed"); } +#[test] +fn expression_aero_5_6() { + let _ = env_logger::try_init(); + + let set_name = create_test_set(EXPECTED); + + let rs = test_filter( + eq( + num_add(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(20), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_ADD Test Failed"); + + let rs = test_filter( + eq( + num_sub(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(20), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_SUB Test Failed"); + + let rs = test_filter( + eq( + num_mul(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(20), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_MUL Test Failed"); + + let rs = test_filter( + gt( + num_div(vec![int_bin("bin".to_string()), int_val(5)]), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 45, "NUM_DIV Test Failed"); + + let rs = test_filter( + eq( + num_pow(float_bin("bin3".to_string()), float_val(2.0)), + float_val(4.0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_POW Test Failed"); + + let rs = test_filter( + eq( + num_log(float_bin("bin3".to_string()), float_val(2.0)), + float_val(4.0), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_LOG Test Failed"); + + let rs = test_filter( + eq(num_mod(int_bin("bin".to_string()), int_val(10)), int_val(0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 10, "NUM_MOD Test Failed"); + + let rs = test_filter( + eq(num_abs(int_bin("bin".to_string())), int_val(1)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "NUM_ABS Test Failed"); + + let rs = test_filter( + eq(num_floor(float_bin("bin3".to_string())), float_val(2.0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 3, "NUM_FLOOR Test Failed"); + + let rs = test_filter( + eq(num_ceil(float_bin("bin3".to_string())), float_val(2.0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 3, "NUM_CEIL Test Failed"); + + let rs = test_filter( + eq(to_int(float_bin("bin3".to_string())), int_val(2)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 3, "TO_INT Test Failed"); + + let rs = test_filter( + eq(to_float(int_bin("bin".to_string())), float_val(2.0)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "TO_FLOAT Test Failed"); + + let rs = test_filter( + eq( + int_and(vec![int_bin("bin".to_string()), int_val(0xff)]), + int_val(0x11), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "INT_AND Test Failed"); + + let rs = test_filter( + eq( + int_xor(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(16), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "INT_XOR Test Failed"); + + let rs = test_filter( + eq(int_not(int_bin("bin".to_string())), int_val(-50)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 1, "INT_NOT Test Failed"); + + let rs = test_filter( + gt( + int_lshift(int_bin("bin".to_string()), int_val(8)), + int_val(0xff), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 99, "INT_LSHIFT Test Failed"); + + let rs = test_filter( + gt( + int_rshift(int_bin("bin".to_string()), int_val(1)), + int_val(0x2a), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 14, "INT_RSHIFT Test Failed"); + + let rs = test_filter( + gt( + int_arshift(int_bin("bin".to_string()), int_val(1)), + int_val(0x2a), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 14, "INT_ARSHIFT Test Failed"); + + let rs = test_filter( + eq(int_count(int_bin("bin".to_string())), int_val(3)), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 32, "INT_COUNT Test Failed"); + + let rs = test_filter( + gt( + int_lscan(int_bin("bin".to_string()), bool_val(true)), + int_val(60), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 7, "INT_LSCAN Test Failed"); + + let rs = test_filter( + gt( + int_rscan(int_bin("bin".to_string()), bool_val(true)), + int_val(60), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 87, "INT_RSCAN Test Failed"); + + let rs = test_filter( + eq( + min(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 90, "MIN Test Failed"); + + let rs = test_filter( + eq( + max(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(10), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 11, "MAX Test Failed"); + + let rs = test_filter( + gt( + cond(vec![ + eq(num_mod(int_bin("bin".to_string()), int_val(2)), int_val(0)), + num_add(vec![int_bin("bin".to_string()), int_val(100)]), + gt(num_mod(int_bin("bin".to_string()), int_val(2)), int_val(0)), + num_add(vec![int_bin("bin".to_string()), int_val(10)]), + int_val(-1), + ]), + int_val(100), + ), + &set_name, + ); + let count = count_results(rs); + assert_eq!(count, 54, "COND Test Failed"); + + let rs = test_filter( + exp_let(vec![ + def("x".to_string(), int_bin("bin".to_string())), + and(vec![ + lt(int_val(5), var("x".to_string())), + lt(var("x".to_string()), int_val(10)), + ]), + ]), + &set_name, + ); + + let count = count_results(rs); + assert_eq!(count, 4, "LET/DEF/VAR Test Failed"); +} + #[test] fn expression_rec_ops() { let _ = env_logger::try_init(); diff --git a/tests/src/exp_op.rs b/tests/src/exp_op.rs new file mode 100644 index 00000000..6187707d --- /dev/null +++ b/tests/src/exp_op.rs @@ -0,0 +1,55 @@ +use crate::common; +use aerospike::expressions::{int_bin, int_val, num_add}; +use aerospike::operations::exp::{read_exp, write_exp, ExpReadFlags, ExpWriteFlags}; +use aerospike::{as_bin, as_key, as_val, Bins, ReadPolicy, WritePolicy}; + +#[test] +fn exp_ops() { + let _ = env_logger::try_init(); + + let client = common::client(); + let namespace = common::namespace(); + let set_name = &common::rand_str(10); + + let policy = ReadPolicy::default(); + + let wpolicy = WritePolicy::default(); + let key = as_key!(namespace, set_name, -1); + let wbin = as_bin!("bin", as_val!(25)); + let bins = vec![&wbin]; + + client.delete(&wpolicy, &key).unwrap(); + + client.put(&wpolicy, &key, &bins).unwrap(); + let rec = client.get(&policy, &key, Bins::All).unwrap(); + assert_eq!( + *rec.bins.get("bin").unwrap(), + as_val!(25), + "EXP OPs init failed" + ); + let flt = num_add(vec![int_bin("bin".to_string()), int_val(4)]); + let ops = &vec![read_exp("example", &flt, ExpReadFlags::Default)]; + let rec = client.operate(&wpolicy, &key, ops); + let rec = rec.unwrap(); + + assert_eq!( + *rec.bins.get("example").unwrap(), + as_val!(29), + "EXP OPs read failed" + ); + + let flt2 = int_bin("bin2".to_string()); + let ops = &vec![ + write_exp("bin2", &flt, ExpWriteFlags::Default), + read_exp("example", &flt2, ExpReadFlags::Default), + ]; + + let rec = client.operate(&wpolicy, &key, ops); + let rec = rec.unwrap(); + + assert_eq!( + *rec.bins.get("example").unwrap(), + as_val!(29), + "EXP OPs write failed" + ); +} diff --git a/tests/src/mod.rs b/tests/src/mod.rs index d48b3813..1d3765d9 100644 --- a/tests/src/mod.rs +++ b/tests/src/mod.rs @@ -22,6 +22,7 @@ mod exp_bitwise; mod exp_hll; mod exp_list; mod exp_map; +mod exp_op; mod hll; mod index; mod kv;