use wbase::num::strict_i32;
use super::list_object::ListObject;
use crate::types::{ObjectInput, object_output::ObjectOutput};
const RESP_ERR_GENERIC_INDEX_OUT_RANGE: &[u8] = b"ERR index out of range";
use wresp::cmd_strings::{
RESP_ERR_GENERIC_NOSUCHKEY, RESP_ERR_GENERIC_SYNTAX_ERROR, RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER,
};
#[inline]
fn arg(input: &ObjectInput, i: usize) -> &[u8] {
input.arg(i)
}
impl ListObject {
pub(crate) fn list_remove(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let count = input.arg1;
output.result1 = i32::MIN as i64;
let item_span = arg(input, 0);
let mut removed_count = 0_i64;
output.result1 = 0;
if count == 0 {
let mut i = 0;
while i < self.list.len() {
if self.list[i].as_slice() == item_span {
let value = self.list.remove(i).unwrap();
self.update_size(&value, false);
removed_count += 1;
} else {
i += 1;
}
}
} else {
let from_head_to_tail = count > 0;
let count = if count == i32::MIN {
i32::MAX as i64
} else {
(count).abs() as i64
};
let mut idx = if from_head_to_tail {
0
} else {
self.list.len() as i64 - 1
};
while removed_count < count && (0..self.list.len() as i64).contains(&idx) {
let matches = self.list[idx as usize].as_slice() == item_span;
if matches {
let value = self.list.remove(idx as usize).unwrap();
self.update_size(&value, false);
removed_count += 1;
if !from_head_to_tail {
idx -= 1;
}
} else {
idx += if from_head_to_tail { 1 } else { -1 };
}
}
}
output.result1 = removed_count;
}
pub(crate) fn list_insert(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
output.result1 = i32::MIN as i64;
if !self.list.is_empty() {
let position = arg(input, 0);
let pivot = arg(input, 1);
let item = arg(input, 2).to_vec();
let insert_before = position.eq_ignore_ascii_case(b"BEFORE");
output.result1 = -1;
if let Some(pos) = self.list.iter().position(|v| v.as_slice() == pivot) {
let at = if insert_before { pos } else { pos + 1 };
self.list.insert(at, item.clone());
self.update_size(&item, true);
output.result1 = self.list.len() as i64;
}
}
}
pub(crate) fn list_index(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let index = input.arg1;
output.result1 = -1;
let len = self.list.len() as i64;
let index = if index < 0 {
len + i64::from(index)
} else {
i64::from(index)
};
if let Some(item) = self.list.get(index as usize) {
output.write_bulk_string(item);
output.result1 = 1;
}
}
pub(crate) fn list_range(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let start = input.arg1;
let stop = input.arg2;
if self.list.is_empty() {
output.write_empty_array();
return;
}
let len = self.list.len() as i64;
let mut start = i64::from(start);
let mut stop = i64::from(stop);
start = if start < 0 { len + start } else { start };
if start < 0 {
start = 0;
}
stop = if stop < 0 { len + stop } else { stop };
if stop >= len {
stop = len - 1;
}
if start > stop {
output.write_empty_array();
return;
}
let count = (stop - start + 1) as usize;
output.write_array_length(count);
for item in self.list.iter().skip(start as usize).take(count) {
output.write_bulk_string(item);
}
output.result1 = count as i64;
}
pub(crate) fn list_trim(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let start = input.arg1;
let end = input.arg2;
if !self.list.is_empty() {
let len = self.list.len() as i64;
let mut start = i64::from(start);
let mut end = i64::from(end);
start = if start < 0 { len + start } else { start };
end = if end < 0 { len + end } else { end };
if start > end || start >= len || end < 0 {
let removed: Vec<Vec<u8>> = self.list.drain(..).collect();
for value in removed {
self.update_size(&value, false);
}
} else {
start = start.max(0);
end = if end >= len { len } else { end + 1 };
if start == 0 {
let num_deletes = len - end;
for _ in 0..num_deletes {
if let Some(value) = self.list.pop_back() {
self.update_size(&value, false);
}
}
output.result1 = num_deletes;
} else {
let doomed: Vec<usize> = (0..len as usize)
.filter(|i| !(*i >= start as usize && *i < end as usize))
.collect();
for (offset, i) in doomed.iter().enumerate() {
let value = self.list.remove(i - offset).unwrap();
self.update_size(&value, false);
}
output.result1 = len;
}
}
}
}
pub(crate) fn list_length(&mut self, output: &mut ObjectOutput) {
output.result1 = self.list.len() as i64;
}
pub(crate) fn list_push(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
f_add_at_head: bool,
) {
output.result1 = 0;
for i in 0..input.parse_state.count {
let value = arg(input, i).to_vec();
if f_add_at_head {
self.list.push_front(value.clone());
} else {
self.list.push_back(value.clone());
}
self.update_size(&value, true);
}
output.result1 = self.list.len() as i64;
}
pub(crate) fn list_pop(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
f_del_at_head: bool,
) {
let mut count = i64::from(input.arg1);
if (self.list.len() as i64) < count {
count = self.list.len() as i64;
}
if self.list.is_empty() {
output.write_null(resp_protocol_version);
count = 0;
} else if count <= 0 {
output.write_empty_array();
} else if count > 1 {
output.write_array_length(count as usize);
}
let mut removed = 0_i64;
while count > 0 && !self.list.is_empty() {
let value = if f_del_at_head {
self.list.pop_front()
} else {
self.list.pop_back()
};
if let Some(value) = value {
self.update_size(&value, false);
output.write_bulk_string(&value);
}
count -= 1;
removed += 1;
}
output.result1 = removed;
}
pub(crate) fn list_set(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
if self.list.is_empty() {
output.write_error(RESP_ERR_GENERIC_NOSUCHKEY.as_bytes());
return;
}
let Some(index) = strict_i32(arg(input, 0)) else {
output.write_error(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
return;
};
let len = self.list.len() as i64;
let index = if index < 0 {
len + i64::from(index)
} else {
i64::from(index)
};
if index > len - 1 || index < 0 {
output.write_error(RESP_ERR_GENERIC_INDEX_OUT_RANGE);
return;
}
let element = arg(input, 1).to_vec();
let old = self.list[index as usize].clone();
self.update_size(&old, false);
self.update_size(&element, true);
self.list[index as usize] = element;
output.payload.extend_from_slice(b"+OK\r\n");
output.result1 = 1;
}
pub(crate) fn list_position(&mut self, input: &ObjectInput, output: &mut ObjectOutput) {
let element = arg(input, 0);
let mut params = ListPositionParams::default();
if let Err(error) = read_list_position_input(input, &mut params) {
output.write_error(error);
return;
}
if params.count < 0 || params.maxlen < 0 || params.rank == 0 {
output.write_error(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
return;
}
let count = if params.count == 0 {
self.list.len() as i64
} else {
params.count
};
let mut found: Vec<i64> = Vec::new();
if params.rank > 0 {
let mut rank = params.rank;
let len = self.list.len() as i64;
let maxlen_index = if params.maxlen == 0 {
len
} else {
params.maxlen
};
for (current_index, item) in self.list.iter().enumerate().take(maxlen_index as usize) {
if item.as_slice() == element {
if rank == 1 {
found.push(current_index as i64);
if found.len() as i64 == count {
break;
}
} else {
rank -= 1;
}
}
}
} else {
let mut rank = params.rank.unsigned_abs() as i64;
let len = self.list.len() as i64;
let maxlen_index = if params.maxlen == 0 {
0
} else {
len - params.maxlen
};
let mut current_index = len - 1;
while current_index >= maxlen_index && current_index >= 0 {
if self.list[current_index as usize].as_slice() == element {
if rank == 1 {
found.push(current_index);
if found.len() as i64 == count {
break;
}
} else {
rank -= 1;
}
}
current_index -= 1;
}
}
let found_len = found.len();
if params.is_default_count {
if found.is_empty() {
output.write_null(2);
} else {
output.write_int64(found[0]);
}
} else if found.is_empty() {
output.write_empty_array();
} else {
output.write_array_length(found_len);
for index in found {
output.write_int64(index);
}
}
output.result1 = found_len as i64;
}
}
#[derive(Debug, Clone, Copy)]
struct ListPositionParams {
rank: i64,
count: i64,
is_default_count: bool,
maxlen: i64,
}
impl Default for ListPositionParams {
fn default() -> Self {
Self {
rank: 1,
count: 1,
is_default_count: true,
maxlen: 0,
}
}
}
fn read_list_position_input(
input: &ObjectInput,
params: &mut ListPositionParams,
) -> Result<(), &'static [u8]> {
let count = input.parse_state.count;
let mut curr_token_idx = 1;
let parse_i32_arg = |idx: &mut usize| -> Result<i64, &'static [u8]> {
if *idx >= count {
return Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes());
}
let val =
strict_i32(arg(input, *idx)).ok_or(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes())?;
*idx += 1;
Ok(i64::from(val))
};
while curr_token_idx < count {
let sb_param = arg(input, curr_token_idx);
curr_token_idx += 1;
if sb_param.eq_ignore_ascii_case(b"RANK") {
params.rank = parse_i32_arg(&mut curr_token_idx)?;
} else if sb_param.eq_ignore_ascii_case(b"COUNT") {
params.count = parse_i32_arg(&mut curr_token_idx)?;
params.is_default_count = false;
} else if sb_param.eq_ignore_ascii_case(b"MAXLEN") {
params.maxlen = parse_i32_arg(&mut curr_token_idx)?;
} else {
return Err(RESP_ERR_GENERIC_SYNTAX_ERROR.as_bytes());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use wval::GarnetObjectType;
use super::*;
use crate::object_store_utils::make_object_input;
#[test]
fn test_read_list_position_input() {
let mut params = ListPositionParams::default();
let input = make_object_input(
GarnetObjectType::List,
0,
&[
b"elem".as_slice(),
b"rAnK",
b"2",
b"cOuNt",
b"5",
b"mAxLeN",
b"100",
],
0,
0,
);
assert!(read_list_position_input(&input, &mut params).is_ok());
assert_eq!(params.rank, 2);
assert_eq!(params.count, 5);
assert!(!params.is_default_count);
assert_eq!(params.maxlen, 100);
for opt in [b"RANK".as_slice(), b"COUNT", b"MAXLEN"] {
let mut p = ListPositionParams::default();
let input = make_object_input(GarnetObjectType::List, 0, &[b"elem".as_slice(), opt], 0, 0);
assert_eq!(
read_list_position_input(&input, &mut p),
Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes())
);
}
let mut p = ListPositionParams::default();
let input = make_object_input(
GarnetObjectType::List,
0,
&[b"elem".as_slice(), b"rank", b"abc"],
0,
0,
);
assert_eq!(
read_list_position_input(&input, &mut p),
Err(RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER.as_bytes())
);
let mut p = ListPositionParams::default();
let input = make_object_input(
GarnetObjectType::List,
0,
&[b"elem".as_slice(), b"UNKNOWN"],
0,
0,
);
assert_eq!(
read_list_position_input(&input, &mut p),
Err(RESP_ERR_GENERIC_SYNTAX_ERROR.as_bytes())
);
}
}