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 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 remove {
store.spop(&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),
}
}
}
}
}
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].to_vec()]) == 0 {
return encode_integer(out, 0);
}
if n <= 0 {
store.del(&[args[1].to_vec()]);
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].to_vec()]) == 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);
}