use crate::store::Store;
use kevy_store::{ScoreBound, ZaddFlags};
use super::util::{
arg_f64, arg_i64, bulk, emit_int, emit_scored, err, fmt_score, int, kevy_err, nil, rest,
wrong_args, ERR_NOT_FLOAT, ERR_NOT_INT, ERR_SYNTAX,
};
const ERR_MIN_MAX: &str = "ERR min or max is not a float";
pub(super) fn dispatch(s: &Store, up: &[u8], argv: &[Vec<u8>], out: &mut Vec<u8>) -> bool {
match up {
b"ZADD" => cmd_zadd(s, argv, out),
b"ZSCORE" => {
if argv.len() == 3 {
match s.zscore(&argv[1], &argv[2]) {
Ok(Some(sc)) => bulk(out, &fmt_score(sc)),
Ok(None) => nil(out),
Err(e) => kevy_err(out, &e),
}
} else {
wrong_args(out, "zscore");
}
}
b"ZCARD" => {
if argv.len() == 2 {
emit_int(out, s.zcard(&argv[1]).map(|n| n as i64));
} else {
wrong_args(out, "zcard");
}
}
b"ZREM" => {
if argv.len() < 3 {
wrong_args(out, "zrem");
} else {
emit_int(out, s.zrem(&argv[1], &rest(argv, 2)).map(|n| n as i64));
}
}
b"ZRANK" => {
if argv.len() == 3 {
match s.zrank(&argv[1], &argv[2]) {
Ok(Some(r)) => int(out, r as i64),
Ok(None) => nil(out),
Err(e) => kevy_err(out, &e),
}
} else {
wrong_args(out, "zrank");
}
}
b"ZINCRBY" => {
if argv.len() != 4 {
wrong_args(out, "zincrby");
} else if let Some(d) = arg_f64(&argv[2]) {
match s.zincrby(&argv[1], d, &argv[3]) {
Ok(sc) => bulk(out, &fmt_score(sc)),
Err(e) => kevy_err(out, &e),
}
} else {
err(out, ERR_NOT_FLOAT);
}
}
b"ZCOUNT" => cmd_zcount(s, argv, out),
b"ZRANGE" => cmd_zrange(s, argv, out, false),
b"ZREVRANGE" => cmd_zrange(s, argv, out, true),
b"ZRANGEBYSCORE" => cmd_zrangebyscore(s, argv, out, false),
b"ZREVRANGEBYSCORE" => cmd_zrangebyscore(s, argv, out, true),
b"ZPOPMIN" => cmd_zpopmin(s, argv, out),
b"ZPOPMIN.BELOW" => cmd_zpopmin_below(s, argv, out),
b"ZREMRANGEBYRANK" => {
if argv.len() != 4 {
wrong_args(out, "zremrangebyrank");
} else if let (Some(a), Some(b)) = (arg_i64(&argv[2]), arg_i64(&argv[3])) {
emit_int(out, s.zremrangebyrank(&argv[1], a, b).map(|n| n as i64));
} else {
err(out, ERR_NOT_INT);
}
}
b"ZREMRANGEBYSCORE" => cmd_zremrangebyscore(s, argv, out),
b"ZSCAN" => cmd_zscan(s, argv, out),
_ => return false,
}
true
}
fn parse_zadd_flags(argv: &[Vec<u8>]) -> Result<(ZaddFlags, bool, usize), &'static str> {
let mut f = ZaddFlags::default();
let mut incr = false;
let mut i = 2;
while i < argv.len() {
let a = &argv[i];
if a.eq_ignore_ascii_case(b"NX") {
f.nx = true;
} else if a.eq_ignore_ascii_case(b"XX") {
f.xx = true;
} else if a.eq_ignore_ascii_case(b"GT") {
f.gt = true;
} else if a.eq_ignore_ascii_case(b"LT") {
f.lt = true;
} else if a.eq_ignore_ascii_case(b"CH") {
f.ch = true;
} else if a.eq_ignore_ascii_case(b"INCR") {
incr = true;
} else {
break;
}
i += 1;
}
if !f.valid() {
return Err("ERR GT, LT, and/or NX options at the same time are not compatible");
}
Ok((f, incr, i))
}
fn cmd_zadd(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
let (flags, incr, first) = match parse_zadd_flags(argv) {
Ok(t) => t,
Err(msg) => return err(out, msg),
};
if argv.len() < first + 2 || !(argv.len() - first).is_multiple_of(2) {
return wrong_args(out, "zadd");
}
let mut pairs: Vec<(f64, &[u8])> = Vec::with_capacity((argv.len() - first) / 2);
let mut i = first;
while i < argv.len() {
let Some(score) = arg_f64(&argv[i]) else {
return err(out, ERR_NOT_FLOAT);
};
pairs.push((score, &argv[i + 1]));
i += 2;
}
if incr {
if pairs.len() != 1 {
return err(out, "ERR INCR option supports a single increment-element pair");
}
return match s.zadd_incr(&argv[1], pairs[0].0, pairs[0].1, flags) {
Ok(Some(next)) => bulk(out, &fmt_score(next)),
Ok(None) => nil(out),
Err(e) => kevy_err(out, &e),
};
}
if flags == ZaddFlags::default() {
return emit_int(out, s.zadd(&argv[1], &pairs).map(|n| n as i64));
}
match s.zadd_flags(&argv[1], &pairs, flags) {
Ok(rep) => int(out, (if flags.ch { rep.changed } else { rep.added }) as i64),
Err(e) => kevy_err(out, &e),
}
}
fn cmd_zcount(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
if argv.len() != 4 {
return wrong_args(out, "zcount");
}
let (Some(min), Some(max)) =
(super::util::parse_score_bound(&argv[2]), super::util::parse_score_bound(&argv[3]))
else {
return err(out, ERR_MIN_MAX);
};
if !min.exclusive && !max.exclusive {
return emit_int(out, s.zcount(&argv[1], min.value, max.value).map(|n| n as i64));
}
emit_int(out, s.zrange_by_score_excl(&argv[1], min, max).map(|v| v.len() as i64));
}
fn cmd_zrange(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>, rev: bool) {
let name = if rev { "zrevrange" } else { "zrange" };
if argv.len() < 4 || argv.len() > 5 {
return wrong_args(out, name);
}
let withscores = argv.len() == 5;
if withscores && !argv[4].eq_ignore_ascii_case(b"WITHSCORES") {
return err(out, ERR_SYNTAX);
}
let (Some(a), Some(b)) = (arg_i64(&argv[2]), arg_i64(&argv[3])) else {
return err(out, ERR_NOT_INT);
};
let res = if rev { s.zrevrange(&argv[1], a, b) } else { s.zrange(&argv[1], a, b) };
match res {
Ok(items) => emit_scored(out, &items, withscores),
Err(e) => kevy_err(out, &e),
}
}
fn parse_range_modifiers(
argv: &[Vec<u8>],
out: &mut Vec<u8>,
) -> Option<(bool, Option<(i64, i64)>)> {
let mut withscores = false;
let mut limit: Option<(i64, i64)> = None;
let mut i = 4;
while i < argv.len() {
let tok = &argv[i];
if tok.eq_ignore_ascii_case(b"WITHSCORES") {
if withscores {
err(out, ERR_SYNTAX);
return None;
}
withscores = true;
i += 1;
} else if tok.eq_ignore_ascii_case(b"LIMIT") {
if limit.is_some() || i + 2 >= argv.len() {
err(out, ERR_SYNTAX);
return None;
}
let (Some(off), Some(cnt)) = (arg_i64(&argv[i + 1]), arg_i64(&argv[i + 2])) else {
err(out, ERR_NOT_INT);
return None;
};
limit = Some((off, cnt));
i += 3;
} else {
err(out, ERR_SYNTAX);
return None;
}
}
Some((withscores, limit))
}
fn cmd_zrangebyscore(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>, rev: bool) {
let name = if rev { "zrevrangebyscore" } else { "zrangebyscore" };
if argv.len() < 4 {
return wrong_args(out, name);
}
let (lo_idx, hi_idx) = if rev { (3, 2) } else { (2, 3) };
let (Some(min), Some(max)) = (
super::util::parse_score_bound(&argv[lo_idx]),
super::util::parse_score_bound(&argv[hi_idx]),
) else {
return err(out, ERR_MIN_MAX);
};
let Some((withscores, limit)) = parse_range_modifiers(argv, out) else {
return;
};
match s.zrange_by_score_excl(&argv[1], min, max) {
Err(e) => kevy_err(out, &e),
Ok(mut items) => {
if rev {
items.reverse();
}
if let Some((off, cnt)) = limit {
apply_limit(&mut items, off, cnt);
}
emit_scored(out, &items, withscores);
}
}
}
fn apply_limit(items: &mut Vec<(Vec<u8>, f64)>, off: i64, cnt: i64) {
let start = off.max(0) as usize;
if start >= items.len() {
items.clear();
} else if cnt < 0 {
items.drain(..start);
} else {
let end = (start + cnt as usize).min(items.len());
*items = items[start..end].to_vec();
}
}
fn cmd_zpopmin(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
if argv.len() < 2 || argv.len() > 3 {
return wrong_args(out, "zpopmin");
}
let count = if argv.len() == 3 {
let Some(c) = arg_i64(&argv[2]) else {
return err(out, ERR_NOT_INT);
};
if c < 0 {
return err(out, "ERR value is out of range, must be positive");
}
c as usize
} else {
1
};
match s.zpopmin(&argv[1], count) {
Ok(items) => emit_scored(out, &items, true),
Err(e) => kevy_err(out, &e),
}
}
fn cmd_zpopmin_below(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
if argv.len() < 3 || argv.len() > 4 {
return wrong_args(out, "zpopmin.below");
}
let Some(below) = arg_f64(&argv[2]) else {
return err(out, ERR_NOT_FLOAT);
};
let count = if argv.len() == 4 {
let Some(c) = arg_i64(&argv[3]) else {
return err(out, ERR_NOT_INT);
};
if c < 0 {
return err(out, "ERR value is out of range, must be positive");
}
c as usize
} else {
1
};
match s.zpopmin_below(&argv[1], below, count) {
Ok(items) => emit_scored(out, &items, true),
Err(e) => kevy_err(out, &e),
}
}
fn cmd_zremrangebyscore(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
if argv.len() != 4 {
return wrong_args(out, "zremrangebyscore");
}
let (Some(min), Some(max)) =
(super::util::parse_score_bound(&argv[2]), super::util::parse_score_bound(&argv[3]))
else {
return err(out, ERR_MIN_MAX);
};
if !min.exclusive && !max.exclusive {
return emit_int(out, s.zremrangebyscore(&argv[1], min.value, max.value).map(|n| n as i64));
}
emit_int(out, zrem_range_excl(s, &argv[1], min, max));
}
fn zrem_range_excl(
s: &Store,
key: &[u8],
min: ScoreBound,
max: ScoreBound,
) -> crate::KevyResult<i64> {
let hits = s.zrange_by_score_excl(key, min, max)?;
if hits.is_empty() {
return Ok(0);
}
let members: Vec<&[u8]> = hits.iter().map(|(m, _)| m.as_slice()).collect();
Ok(s.zrem(key, &members)? as i64)
}
fn cmd_zscan(s: &Store, argv: &[Vec<u8>], out: &mut Vec<u8>) {
if argv.len() < 3 {
return wrong_args(out, "zscan");
}
if arg_i64(&argv[2]).is_none() {
return err(out, ERR_NOT_INT);
}
let Some(pat) = super::parse_match_count(argv, 3) else {
return err(out, ERR_SYNTAX);
};
match s.zrange(&argv[1], 0, -1) {
Err(e) => kevy_err(out, &e),
Ok(items) => {
let mut flat: Vec<Vec<u8>> = Vec::with_capacity(items.len() * 2);
for (m, sc) in items {
if pat.as_ref().is_none_or(|p| kevy_store::glob_match(p, &m)) {
flat.push(m);
flat.push(fmt_score(sc));
}
}
super::emit_scan_page(out, b"0", &flat);
}
}
}