use crate::cmd::{ERR_NOT_INT, arg_i64, emit_int_result, store_err, wrong_args};
use kevy_resp::{
ArgvView, encode_array_len, encode_bulk, encode_error, encode_integer, encode_null_bulk,
encode_simple_string,
};
use kevy_store::Store;
use std::time::Duration;
pub(crate) fn cmd_spop_rand<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
remove: bool,
out: &mut Vec<u8>,
) {
let name = if remove { "spop" } else { "srandmember" };
if args.len() < 2 || args.len() > 3 {
return wrong_args(out, name);
}
let count_given = args.len() == 3;
let raw = match parse_spop_count(args, count_given, remove, out) {
Some(c) => c,
None => return, };
let with_repeats = raw < 0;
let count = raw.unsigned_abs() as usize;
let res = if remove {
let res = store.spop(&args[1], count);
if let Ok(popped) = &res {
set_spop_propagation(&args[1], popped);
}
res
} else if with_repeats {
store.srandmember_with_repeats(&args[1], count)
} else {
store.srandmember(&args[1], count)
};
match res {
Err(e) => store_err(out, e),
Ok(items) => {
if count_given {
encode_array_len(out, items.len() as i64);
for it in &items {
encode_bulk(out, it);
}
} else {
match items.first() {
Some(v) => encode_bulk(out, v),
None => encode_null_bulk(out),
}
}
}
}
}
fn set_spop_propagation(key: &[u8], popped: &[Vec<u8>]) {
use kevy_rt::propagation::{Propagate, set_override};
if popped.is_empty() {
set_override(Propagate::Suppress);
return;
}
let mut frame: Vec<Vec<u8>> = Vec::with_capacity(2 + popped.len());
frame.push(b"SREM".to_vec());
frame.push(key.to_vec());
frame.extend(popped.iter().cloned());
set_override(Propagate::Replace(frame));
}
fn parse_spop_count<A: ArgvView + ?Sized>(
args: &A,
count_given: bool,
remove: bool,
out: &mut Vec<u8>,
) -> Option<i64> {
let raw = if count_given {
match arg_i64(&args[2]) {
Some(c) => c,
None => {
encode_error(out, ERR_NOT_INT);
return None;
}
}
} else {
1
};
if raw < 0 && remove {
encode_error(out, "ERR value is out of range, must be positive");
return None;
}
Some(raw)
}
pub(crate) fn cmd_blpop<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
tail: bool,
out: &mut Vec<u8>,
) {
let name = if tail { "brpop" } else { "blpop" };
if args.len() < 3 {
return wrong_args(out, name);
}
let timeout_idx = args.len() - 1;
let valid = std::str::from_utf8(&args[timeout_idx])
.ok()
.and_then(|s| s.parse::<f64>().ok())
.is_some_and(|f| f.is_finite() && f >= 0.0);
if !valid {
return encode_error(out, "ERR timeout is not a float or out of range");
}
if args.len() > 3 {
return;
}
let res = if tail { store.rpop(&args[1], 1) } else { store.lpop(&args[1], 1) };
match res {
Err(e) => store_err(out, e),
Ok(items) => {
if let Some(v) = items.into_iter().next() {
encode_array_len(out, 2);
encode_bulk(out, &args[1]);
encode_bulk(out, &v);
}
}
}
}
pub(crate) fn cmd_pop<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
tail: bool,
out: &mut Vec<u8>,
) {
let name = if tail { "rpop" } else { "lpop" };
if args.len() < 2 || args.len() > 3 {
return wrong_args(out, name);
}
let count_given = args.len() == 3;
let count = if count_given {
match arg_i64(&args[2]) {
Some(c) if c >= 0 => c as usize,
_ => return encode_error(out, "ERR value is out of range, must be positive"),
}
} else {
1
};
let res = if tail { store.rpop(&args[1], count) } else { store.lpop(&args[1], count) };
match res {
Err(e) => store_err(out, e),
Ok(items) => {
if count_given {
if items.is_empty() {
out.extend_from_slice(b"*-1\r\n"); } else {
encode_array_len(out, items.len() as i64);
for it in &items {
encode_bulk(out, it);
}
}
} else {
match items.first() {
Some(v) => encode_bulk(out, v),
None => encode_null_bulk(out),
}
}
}
}
}
pub(crate) fn cmd_set<A: ArgvView + ?Sized>(store: &mut Store, args: &A, out: &mut Vec<u8>) {
if args.len() < 3 {
return wrong_args(out, "set");
}
let mut expire: Option<Duration> = None;
let mut nx = false;
let mut xx = false;
let mut i = 3;
while i < args.len() {
match args[i].to_ascii_uppercase().as_slice() {
b"NX" => nx = true,
b"XX" => xx = true,
opt @ (b"EX" | b"PX") => {
let Some(raw) = args.get(i + 1) else {
return encode_error(out, "ERR syntax error");
};
let Some(n) = arg_i64(raw).filter(|&n| n > 0) else {
return encode_error(out, "ERR invalid expire time in 'set' command");
};
let ms = if opt == b"EX" { n.saturating_mul(1000) } else { n };
expire = Some(Duration::from_millis(ms as u64));
i += 1;
}
_ => return encode_error(out, "ERR syntax error"),
}
i += 1;
}
if nx && xx {
return encode_error(out, "ERR syntax error");
}
if store.set_slice(&args[1], &args[2], expire, nx, xx) {
encode_simple_string(out, "OK");
} else {
encode_null_bulk(out); }
}
pub(crate) fn cmd_setex<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
unit_ms: i64,
name: &str,
out: &mut Vec<u8>,
) {
if args.len() != 4 {
return wrong_args(out, name);
}
let Some(n) = arg_i64(&args[2]).filter(|&n| n > 0) else {
return encode_error(out, &format!("ERR invalid expire time in '{name}' command"));
};
let ms = n.saturating_mul(unit_ms) as u64;
store.set_slice(&args[1], &args[3], Some(Duration::from_millis(ms)), false, false);
encode_simple_string(out, "OK");
}
pub(crate) fn cmd_incr<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
delta: i64,
cmd: &str,
out: &mut Vec<u8>,
) {
if args.len() != 2 {
return wrong_args(out, cmd);
}
emit_int_result(store.incr_by(&args[1], delta), out);
}
pub(crate) fn cmd_incr_by<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
negate: bool,
cmd: &str,
out: &mut Vec<u8>,
) {
if args.len() != 3 {
return wrong_args(out, cmd);
}
let Some(mut delta) = arg_i64(&args[2]) else {
return encode_error(out, ERR_NOT_INT);
};
if negate {
let Some(neg) = delta.checked_neg() else {
return encode_error(out, "ERR decrement would overflow");
};
delta = neg;
}
emit_int_result(store.incr_by(&args[1], delta), out);
}
pub(crate) fn cmd_expire<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
unit_ms: i64,
cmd: &str,
out: &mut Vec<u8>,
) {
if args.len() != 3 {
return wrong_args(out, cmd);
}
let Some(n) = arg_i64(&args[2]) else {
return encode_error(out, ERR_NOT_INT);
};
if store.exists(&[&args[1]]) == 0 {
return encode_integer(out, 0);
}
if n <= 0 {
store.del(&[&args[1]]);
return encode_integer(out, 1);
}
let ms = n.saturating_mul(unit_ms) as u64;
encode_integer(out, i64::from(store.expire(&args[1], Duration::from_millis(ms))));
}
pub(crate) fn cmd_expireat<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
unit_ms: i64,
cmd: &str,
out: &mut Vec<u8>,
) {
if args.len() != 3 {
return wrong_args(out, cmd);
}
let Some(n) = arg_i64(&args[2]) else {
return encode_error(out, ERR_NOT_INT);
};
if store.exists(&[&args[1]]) == 0 {
return encode_integer(out, 0);
}
let deadline_ms = n.saturating_mul(unit_ms).max(0) as u64;
encode_integer(out, i64::from(store.expire_at_unix_ms(&args[1], deadline_ms)));
}
pub(crate) fn cmd_ttl<A: ArgvView + ?Sized>(
store: &mut Store,
args: &A,
in_secs: bool,
cmd: &str,
out: &mut Vec<u8>,
) {
if args.len() != 2 {
return wrong_args(out, cmd);
}
let ms = store.pttl(&args[1]);
let val = if in_secs && ms >= 0 { (ms + 500) / 1000 } else { ms };
encode_integer(out, val);
}