use yo_common::{Code, Error, Result};
use yo_kv::{End, Entry, Keyspace};
use super::args::{self, Args};
use super::table::Spec;
use crate::reply::Out;
const BAD_POP_COUNT: &str = "value is out of range, must be positive";
pub(super) const BAD_NUMKEYS: &str = "numkeys should be greater than 0";
pub(super) const BAD_MPOP_COUNT: &str = "count should be greater than 0";
const BAD_COUNT: &str = "COUNT can't be negative";
const BAD_MAXLEN: &str = "MAXLEN can't be negative";
pub(super) fn execute(db: &mut Keyspace, spec: &Spec, args: Args<'_>, out: &mut Out) -> Result<()> {
match spec.name {
"lpush" => out.int(count(db.push(args.get(1), End::Left, rest(args))?)),
"rpush" => out.int(count(db.push(args.get(1), End::Right, rest(args))?)),
"lpushx" => out.int(count(db.pushx(args.get(1), End::Left, rest(args))?)),
"rpushx" => out.int(count(db.pushx(args.get(1), End::Right, rest(args))?)),
"lpop" => pop(db, spec, args, End::Left, out)?,
"rpop" => pop(db, spec, args, End::Right, out)?,
"llen" => out.int(count(db.llen(args.get(1))?)),
"lrange" => {
let (start, stop) = (args.int(2)?, args.int(3)?);
let mark = out.len();
let mut n = 0;
for e in db.lrange(args.get(1), start, stop)? {
element(out, e);
n += 1;
}
out.close_array(mark, n);
}
"lindex" => match db.lindex(args.get(1), args.int(2)?)? {
Some(e) => element(out, e),
None => out.nil(),
},
"lset" => {
db.lset(args.get(1), args.int(2)?, args.get(3))?;
out.ok();
}
"linsert" => {
let before = if args::is(args.get(2), b"before") {
true
} else if args::is(args.get(2), b"after") {
false
} else {
return Err(args::syntax());
};
out.int(db.linsert(args.get(1), before, args.get(3), args.get(4))?);
}
"lrem" => out.int(count(db.lrem(args.get(1), args.int(2)?, args.get(3))?)),
"ltrim" => {
db.ltrim(args.get(1), args.int(2)?, args.int(3)?)?;
out.ok();
}
"lpos" => lpos(db, args, out)?,
"rpoplpush" => moved(db, args.get(1), args.get(2), End::Right, End::Left, out)?,
"lmove" => {
let from = end_of(args.get(3))?;
let to = end_of(args.get(4))?;
moved(db, args.get(1), args.get(2), from, to, out)?;
}
"lmpop" => mpop(db, args, out)?,
other => unreachable!("the table sent {other} to the list group"),
}
Ok(())
}
fn pop(db: &mut Keyspace, spec: &Spec, args: Args<'_>, end: End, out: &mut Out) -> Result<()> {
let key = args.get(1);
if args.len() == 2 {
let mut got = false;
db.pop_into(key, end, 1, |e| {
element(out, e);
got = true;
})?;
if !got {
out.nil();
}
return Ok(());
}
if args.len() != 3 {
return Err(args::wrong_arity(spec.name));
}
let want = match args.int(2) {
Ok(n) if n >= 0 => usize::try_from(n).unwrap_or(usize::MAX),
_ => return Err(Error::new(Code::Invalid, BAD_POP_COUNT)),
};
if db.llen(key)? == 0 {
out.nil_array();
return Ok(());
}
let mark = out.len();
let n = db.pop_into(key, end, want, |e| element(out, e))?;
out.close_array(mark, n);
Ok(())
}
fn lpos(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let mut rank = 1i64;
let mut wanted: Option<usize> = None;
let mut maxlen = 0usize;
let mut i = 3;
while i < args.len() {
if i + 1 >= args.len() {
return Err(args::syntax());
}
let opt = args.get(i);
if args::is(opt, b"rank") {
rank = args.int(i + 1)?;
} else if args::is(opt, b"count") {
wanted = Some(non_negative(args.int(i + 1)?, BAD_COUNT)?);
} else if args::is(opt, b"maxlen") {
maxlen = non_negative(args.int(i + 1)?, BAD_MAXLEN)?;
} else {
return Err(args::syntax());
}
i += 2;
}
let (key, want) = (args.get(1), args.get(2));
match wanted {
Some(n) => {
let mark = out.len();
let found = db.lpos_into(key, want, rank, n, maxlen, |at| out.int(count(at)))?;
out.close_array(mark, found);
}
None => {
let mut found = None;
db.lpos_into(key, want, rank, 1, maxlen, |at| found = Some(at))?;
match found {
Some(at) => out.int(count(at)),
None => out.nil(),
}
}
}
Ok(())
}
fn mpop(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let numkeys = match args.int(1) {
Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
_ => return Err(Error::new(Code::Invalid, BAD_NUMKEYS)),
};
if numkeys >= args.len() - 2 {
return Err(args::syntax());
}
let at = 2 + numkeys;
let end = end_of(args.get(at))?;
let mut want = 1usize;
if at + 1 < args.len() {
if args.len() != at + 3 || !args::is(args.get(at + 1), b"count") {
return Err(args::syntax());
}
want = match args.int(at + 2) {
Ok(n) if n > 0 => usize::try_from(n).unwrap_or(usize::MAX),
_ => return Err(Error::new(Code::Invalid, BAD_MPOP_COUNT)),
};
}
for i in 2..at {
let key = args.get(i);
if db.llen(key)? == 0 {
continue;
}
out.array(2);
out.bulk(key);
let mark = out.len();
let n = db.pop_into(key, end, want, |e| element(out, e))?;
out.close_array(mark, n);
return Ok(());
}
out.nil_array();
Ok(())
}
fn moved(
db: &mut Keyspace,
src: &[u8],
dst: &[u8],
from: End,
to: End,
out: &mut Out,
) -> Result<()> {
match db.lmove(src, dst, from, to)? {
Some(v) => out.bulk(v),
None => out.nil(),
}
Ok(())
}
pub(super) fn end_of(arg: &[u8]) -> Result<End> {
if args::is(arg, b"left") {
Ok(End::Left)
} else if args::is(arg, b"right") {
Ok(End::Right)
} else {
Err(args::syntax())
}
}
fn non_negative(n: i64, msg: &'static str) -> Result<usize> {
if n < 0 {
return Err(Error::new(Code::Invalid, msg));
}
Ok(usize::try_from(n).unwrap_or(usize::MAX))
}
#[inline]
fn rest(args: Args<'_>) -> impl Iterator<Item = &[u8]> + Clone {
(2..args.len()).map(move |i| args.get(i))
}
#[inline]
fn element(out: &mut Out, e: Entry<'_>) {
match e {
Entry::Int(n) => out.bulk_int(n),
Entry::Str(s) => out.bulk(s),
}
}
#[inline]
fn count(n: usize) -> i64 {
i64::try_from(n).unwrap_or(i64::MAX)
}