use itoa::Buffer;
use wbase::{
convert::{
milliseconds_from_diff_utc_now_ticks, seconds_from_diff_utc_now_ticks,
unix_time_in_milliseconds_from_ticks, unix_time_in_seconds_from_ticks,
},
num::{try_parse_f64, try_parse_i64},
};
use wresp::{
ExpirationWithOption,
cmd_strings::{
RESP_ERR_GENERIC_NAN_INFINITY_INCR, RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER,
RESP_ERR_NOT_VALID_FLOAT,
},
};
use zmij::Buffer as ZmijBuffer;
use super::hash_object::{
HashObject, HashOperation, pick_k_random_indexes, pick_random_index, scan_operate_shared,
};
use crate::{
parse_utils::try_parse_with_infinity,
types::{ObjectInput, object_output::ObjectOutput},
};
pub(crate) const RESP_ERR_HASH_VALUE_IS_NOT_INTEGER: &[u8] = b"ERR hash value is not an integer.";
pub(crate) const RESP_ERR_HASH_VALUE_IS_NOT_FLOAT: &[u8] = b"ERR hash value is not a float.";
pub(crate) const RESP_ERR_GENERIC_NAN_INFINITY: &[u8] = b"ERR value is NaN or Infinity";
#[inline]
fn arg(input: &ObjectInput, i: usize) -> &[u8] {
input.parse_state.get_arg_slice_by_ref(i).as_slice()
}
#[inline]
fn get_byte_span_from_input(input: &ObjectInput, index: usize) -> &[u8] {
arg(input, index)
}
fn num_utils_try_parse_long(v: &[u8]) -> Option<i64> {
let mut value = 0;
try_parse_i64(v, &mut value).then_some(value)
}
fn num_utils_try_parse_double(v: &[u8]) -> Option<f64> {
let mut value = 0.0;
try_parse_f64(v, &mut value).then_some(value)
}
#[inline]
fn format_double(value: f64) -> Vec<u8> {
let mut buf = ZmijBuffer::new();
wresp::format_double(value, &mut buf).as_bytes().to_vec()
}
impl HashObject {
pub(crate) fn hash_get(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
) {
let key = get_byte_span_from_input(input, 0);
match self.try_get_value(key) {
Some(hash_value) => output.write_bulk_string(hash_value),
None => output.write_null(resp_protocol_version),
}
output.result1 = 1;
}
pub(crate) fn hash_multiple_get(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
) {
output.write_array_length(input.parse_state.count);
for i in 0..input.parse_state.count {
let key = get_byte_span_from_input(input, i);
match self.try_get_value(key) {
Some(hash_value) => output.write_bulk_string(hash_value),
None => output.write_null(resp_protocol_version),
}
}
output.result1 = input.parse_state.count as i64;
}
pub(crate) fn hash_get_all(&mut self, output: &mut ObjectOutput, resp_protocol_version: u8) {
write_map_length(output, self.count(), resp_protocol_version);
let is_expirable = self.has_expirable_items();
for (key, value) in self.hash.iter() {
if is_expirable && self.is_expired(key) {
continue;
}
output.write_bulk_string(key);
output.write_bulk_string(value);
}
}
pub(crate) fn hash_delete(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let mut removed = 0_i64;
for i in 0..input.parse_state.count {
let key = get_byte_span_from_input(input, i);
if self.remove(key).is_some() {
removed += 1;
}
}
output.result1 = removed;
}
pub(crate) fn hash_length(&mut self, output: &mut ObjectOutput) {
output.result1 = self.count() as i64;
}
pub(crate) fn hash_str_length(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let key = get_byte_span_from_input(input, 0);
output.result1 = match self.try_get_value(key) {
Some(hash_value) => hash_value.len() as i64,
None => 0,
};
}
pub(crate) fn hash_exists(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let field = get_byte_span_from_input(input, 0);
output.result1 = i64::from(self.contains_key(field));
}
pub(crate) fn hash_random_field(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
) {
let mut count_parameter = (input.arg1 >> 2) as i64;
let with_values = (input.arg1 & 1) == 1;
let included_count = ((input.arg1 >> 1) & 1) == 1;
let seed = input.arg2;
let mut count_done = 0_i64;
if included_count {
let count = self.count();
if count == 0 {
output.write_empty_array();
output.result1 = 0;
return;
}
if count_parameter > 0 && count_parameter > count as i64 {
count_parameter = count as i64;
}
let index_count = count_parameter.unsigned_abs() as usize;
let indexes = pick_k_random_indexes(count, index_count, seed, count_parameter > 0);
output.write_array_length(if with_values && resp_protocol_version == 2 {
index_count * 2
} else {
index_count
});
for index in indexes {
let Some((key, value)) = self.element_at(index) else {
continue;
};
if resp_protocol_version >= 3 && with_values {
output.write_array_length(2);
}
output.write_bulk_string(&key);
if with_values {
output.write_bulk_string(&value);
}
count_done += 1;
}
} else {
let count = self.count();
if count == 0 {
output.write_null(resp_protocol_version);
output.result1 = 0;
return;
}
let index = pick_random_index(count, seed);
if let Some((key, _)) = self.element_at(index) {
output.write_bulk_string(&key);
}
count_done = 1;
}
output.result1 = count_done;
}
pub(crate) fn hash_set(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
self.delete_expired_items();
let mut set = 0_i64;
let hash_op = input.header.sub_id();
let mut i = 0;
while i < input.parse_state.count {
let key = get_byte_span_from_input(input, i);
let value = arg(input, i + 1);
match self.hash.get(key) {
None => {
self.hash.insert(key.to_vec(), value.to_vec());
self.update_size(key, value, true);
set += 1;
}
Some(old_value) => {
let old_value = old_value.clone();
if matches!(
HashOperation::try_from(hash_op),
Ok(HashOperation::Hset) | Ok(HashOperation::Hmset)
) {
self.heap_memory_size +=
value.len().div_ceil(8) as i64 * 8 - old_value.len().div_ceil(8) as i64 * 8;
self.hash.insert(key.to_vec(), value.to_vec());
if self.has_expirable_items()
&& self
.expiration_times
.as_mut()
.unwrap()
.remove(key)
.is_some()
{
self.heap_memory_size -= 16 + 16;
self.cleanup_expiration_structures_if_empty();
}
}
}
}
i += 2;
}
output.result1 = set;
}
pub fn hash_collect(&mut self, output: &mut ObjectOutput) {
self.delete_expired_items();
output.result1 = 1;
}
pub(crate) fn hash_get_keys_or_values(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let count = self.count();
let Ok(op) = HashOperation::try_from(input.header.sub_id()) else {
return;
};
output.write_array_length(count);
let is_expirable = self.has_expirable_items();
let mut written = 0_i64;
for (key, value) in self.hash.iter() {
if is_expirable && self.is_expired(key) {
continue;
}
if op == HashOperation::Hkeys {
output.write_bulk_string(key);
} else {
output.write_bulk_string(value);
}
written += 1;
}
output.result1 = written;
}
pub(crate) fn hash_increment(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
output.result1 = i32::MIN as i64;
let key = get_byte_span_from_input(input, 0);
let incr_slice = arg(input, 1);
let Some(incr) = num_utils_try_parse_long(incr_slice) else {
output.write_error(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
return;
};
self.delete_expired_items();
match self.hash.get(key).cloned() {
None => {
self.add(key, incr_slice.to_vec());
write_integer_from_bytes(output, incr_slice);
}
Some(hash_value) => {
let Some(result) = num_utils_try_parse_long(&hash_value) else {
output.write_error(RESP_ERR_HASH_VALUE_IS_NOT_INTEGER);
return;
};
let result = result.wrapping_add(incr);
let formatted_value = format_i64(result);
self.replace_value(key, &hash_value, &formatted_value);
write_integer_from_bytes(output, &formatted_value);
}
}
output.result1 = 1;
}
pub(crate) fn hash_increment_float(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
output.result1 = i32::MIN as i64;
let key = get_byte_span_from_input(input, 0);
let incr_slice = arg(input, 1);
let Some(incr) = num_utils_try_parse_double(incr_slice) else {
output.write_error(RESP_ERR_NOT_VALID_FLOAT.as_bytes());
return;
};
if incr.is_infinite() {
output.write_error(RESP_ERR_GENERIC_NAN_INFINITY);
return;
}
self.delete_expired_items();
match self.hash.get(key).cloned() {
None => {
self.add(key, incr_slice.to_vec());
output.write_bulk_string(incr_slice);
}
Some(hash_value) => {
let Some(result) = try_parse_with_infinity(&hash_value) else {
output.write_error(RESP_ERR_HASH_VALUE_IS_NOT_FLOAT);
return;
};
if result.is_infinite() {
output.write_error(RESP_ERR_GENERIC_NAN_INFINITY_INCR.as_bytes());
return;
}
let result = result + incr;
let formatted_value = format_double(result);
self.replace_value(key, &hash_value, &formatted_value);
output.write_bulk_string(&formatted_value);
}
}
output.result1 = 1;
}
pub(crate) fn hash_expire(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
self.delete_expired_items();
let expiration_with_option = ExpirationWithOption::from_word_head_tail(input.arg1, input.arg2);
output.write_array_length(input.parse_state.count);
for i in 0..input.parse_state.count {
let result = self.set_expiration(
get_byte_span_from_input(input, i),
expiration_with_option.expiration_time_in_ticks(),
expiration_with_option.expire_option(),
);
output.write_int64(i64::from(result as i32));
}
output.result1 = input.parse_state.count as i64;
}
pub(crate) fn hash_time_to_live(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
self.delete_expired_items();
let is_milliseconds = input.arg1 == 1;
let is_timestamp = input.arg2 == 1;
let num_fields = input.parse_state.count;
output.write_array_length(num_fields);
for i in 0..num_fields {
let mut result = self.get_expiration(get_byte_span_from_input(input, i));
if result >= 0 {
if is_timestamp && is_milliseconds {
result = unix_time_in_milliseconds_from_ticks(result);
} else if is_timestamp && !is_milliseconds {
result = unix_time_in_seconds_from_ticks(result);
} else if !is_timestamp && is_milliseconds {
result = milliseconds_from_diff_utc_now_ticks(result);
} else if !is_timestamp && !is_milliseconds {
result = seconds_from_diff_utc_now_ticks(result);
}
}
output.write_int64(result);
}
output.result1 = num_fields as i64;
}
pub(crate) fn hash_persist(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
self.delete_expired_items();
let num_fields = input.parse_state.count;
output.write_array_length(num_fields);
for i in 0..num_fields {
let result = self.persist(get_byte_span_from_input(input, i));
output.write_int64(i64::from(result));
}
output.result1 = num_fields as i64;
}
fn replace_value(&mut self, key: &[u8], old_value: &[u8], new_value: &[u8]) {
self.heap_memory_size +=
new_value.len().div_ceil(8) as i64 * 8 - old_value.len().div_ceil(8) as i64 * 8;
self.hash.insert(key.to_vec(), new_value.to_vec());
}
pub(crate) fn scan_operate(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
scan_operate_shared(input, output, |cursor, count, pattern, is_no_value| {
self.scan(cursor, count, pattern, is_no_value)
});
}
}
#[inline]
fn write_map_length(output: &mut ObjectOutput, len: usize, resp_protocol_version: u8) {
output.write_map_length(len, resp_protocol_version);
}
#[inline]
fn write_integer_from_bytes(output: &mut ObjectOutput, value: &[u8]) {
output.payload.push(b':');
output.payload.extend_from_slice(value);
output.payload.extend_from_slice(b"\r\n");
}
#[inline]
fn format_i64(value: i64) -> Vec<u8> {
let mut buf = Buffer::new();
buf.format(value).as_bytes().to_vec()
}