use super::{parse_int, rand_u64, upper, wrong_args};
use crate::db::{now_ms, Db, Keyspace};
use crate::resp::Frame;
use crate::server::ConnState;
use bytes::Bytes;
pub fn del(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() < 2 {
return wrong_args("del");
}
let n = args[1..].iter().filter(|k| db.remove(k)).count();
Frame::Integer(n as i64)
}
pub fn exists(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() < 2 {
return wrong_args("exists");
}
let n = args[1..].iter().filter(|k| db.contains(k)).count();
Frame::Integer(n as i64)
}
pub fn expire(db: &mut Db, args: &[Bytes], unit_ms: i64, relative: bool) -> Frame {
if args.len() < 3 {
return wrong_args("expire");
}
let n = match parse_int(&args[2]) {
Ok(n) => n,
Err(e) => return e,
};
let mut nx = false;
let mut xx = false;
let mut gt = false;
let mut lt = false;
for opt in &args[3..] {
match upper(opt).as_str() {
"NX" => nx = true,
"XX" => xx = true,
"GT" => gt = true,
"LT" => lt = true,
_ => return Frame::err("Unsupported option"),
}
}
if (nx && (xx || gt || lt)) || (gt && lt) {
return Frame::err("NX and XX, GT or LT options at the same time are not compatible");
}
if !db.contains(&args[1]) {
return Frame::Integer(0);
}
let now = now_ms() as i128;
let at = if relative {
now + n as i128 * unit_ms as i128
} else {
n as i128 * unit_ms as i128
};
let current = db.expire_at(&args[1]).map(|v| v as i128);
let ok = if nx {
current.is_none()
} else if xx {
current.is_some()
} else if gt {
matches!(current, Some(c) if at > c)
} else if lt {
current.map_or(true, |c| at < c)
} else {
true
};
if !ok {
return Frame::Integer(0);
}
if at <= now {
db.remove(&args[1]);
} else {
let at = at.min(u64::MAX as i128) as u64;
db.set_expire(&args[1], at);
}
Frame::Integer(1)
}
pub fn ttl(db: &mut Db, args: &[Bytes], seconds: bool) -> Frame {
if args.len() != 2 {
return wrong_args("ttl");
}
if !db.contains(&args[1]) {
return Frame::Integer(-2);
}
match db.expire_at(&args[1]) {
None => Frame::Integer(-1),
Some(at) => {
let remaining = at.saturating_sub(now_ms());
if seconds {
Frame::Integer(((remaining + 500) / 1000) as i64)
} else {
Frame::Integer(remaining as i64)
}
}
}
}
pub fn expiretime(db: &mut Db, args: &[Bytes], seconds: bool) -> Frame {
if args.len() != 2 {
return wrong_args("expiretime");
}
if !db.contains(&args[1]) {
return Frame::Integer(-2);
}
match db.expire_at(&args[1]) {
None => Frame::Integer(-1),
Some(at) => Frame::Integer(if seconds {
(at / 1000) as i64
} else {
at as i64
}),
}
}
pub fn persist(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() != 2 {
return wrong_args("persist");
}
Frame::Integer(db.persist(&args[1]) as i64)
}
pub fn keys(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() != 2 {
return wrong_args("keys");
}
let matched = db.keys_matching(Some(&args[1]));
Frame::Array(matched.into_iter().map(Frame::Bulk).collect())
}
pub fn scan(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() < 2 {
return wrong_args("scan");
}
let mut pattern: Option<Bytes> = None;
let mut type_filter: Option<String> = None;
let mut i = 2;
while i < args.len() {
match upper(&args[i]).as_str() {
"MATCH" if i + 1 < args.len() => {
pattern = Some(args[i + 1].clone());
i += 2;
}
"COUNT" if i + 1 < args.len() => {
if parse_int(&args[i + 1]).is_err() {
return Frame::err("value is not an integer or out of range");
}
i += 2;
}
"TYPE" if i + 1 < args.len() => {
type_filter = Some(upper(&args[i + 1]).to_lowercase());
i += 2;
}
_ => return Frame::err("syntax error"),
}
}
let mut keys = db.keys_matching(pattern.as_deref());
if let Some(t) = type_filter {
keys.retain(|k| db.get(k).map(|v| v.type_name()) == Some(t.as_str()));
}
Frame::Array(vec![
Frame::bulk("0"),
Frame::Array(keys.into_iter().map(Frame::Bulk).collect()),
])
}
pub fn type_cmd(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() != 2 {
return wrong_args("type");
}
let name = db.get(&args[1]).map(|v| v.type_name()).unwrap_or("none");
Frame::Simple(name.into())
}
pub fn rename(db: &mut Db, args: &[Bytes], nx: bool) -> Frame {
if args.len() != 3 {
return wrong_args(if nx { "renamenx" } else { "rename" });
}
if !db.contains(&args[1]) {
return Frame::err("no such key");
}
if nx && db.contains(&args[2]) {
return Frame::Integer(0);
}
db.rename(&args[1], args[2].clone());
if nx {
Frame::Integer(1)
} else {
Frame::ok()
}
}
pub fn randomkey(db: &mut Db, args: &[Bytes]) -> Frame {
if args.len() != 1 {
return wrong_args("randomkey");
}
let keys = db.all_keys();
if keys.is_empty() {
Frame::Null
} else {
let idx = (rand_u64() % keys.len() as u64) as usize;
Frame::Bulk(keys[idx].clone())
}
}
pub fn copy(ks: &mut Keyspace, conn: &ConnState, args: &[Bytes]) -> Frame {
if args.len() < 3 {
return wrong_args("copy");
}
let mut replace = false;
let mut dst_db = conn.db_index;
let mut i = 3;
while i < args.len() {
match upper(&args[i]).as_str() {
"REPLACE" => {
replace = true;
i += 1;
}
"DB" if i + 1 < args.len() => {
let n = match parse_int(&args[i + 1]) {
Ok(n) => n,
Err(e) => return e,
};
if !ks.is_valid(n) {
return Frame::err("DB index is out of range");
}
dst_db = n as usize;
i += 2;
}
_ => return Frame::err("syntax error"),
}
}
let (src_key, dst_key) = (&args[1], &args[2]);
if dst_db == conn.db_index && src_key == dst_key {
return Frame::err("source and destination objects are the same");
}
let src = ks.db(conn.db_index);
let value = match src.get(src_key).cloned() {
Some(v) => v,
None => return Frame::Integer(0),
};
let ttl = src.expire_at(src_key);
let dst = ks.db(dst_db);
if dst.contains(dst_key) && !replace {
return Frame::Integer(0);
}
dst.put(dst_key.clone(), value, ttl);
Frame::Integer(1)
}
pub fn move_key(ks: &mut Keyspace, conn: &ConnState, args: &[Bytes]) -> Frame {
if args.len() != 3 {
return wrong_args("move");
}
let n = match parse_int(&args[2]) {
Ok(n) => n,
Err(e) => return e,
};
if !ks.is_valid(n) {
return Frame::err("DB index is out of range");
}
let dst_db = n as usize;
if dst_db == conn.db_index {
return Frame::err("source and destination objects are the same");
}
let (src, dst) = ks.pair(conn.db_index, dst_db);
if dst.contains(&args[1]) {
return Frame::Integer(0);
}
match src.take(&args[1]) {
Some((value, expire_at)) => {
dst.put(args[1].clone(), value, expire_at);
Frame::Integer(1)
}
None => Frame::Integer(0),
}
}
pub fn swapdb(ks: &mut Keyspace, args: &[Bytes]) -> Frame {
if args.len() != 3 {
return wrong_args("swapdb");
}
let first = match parse_int(&args[1]) {
Ok(n) => n,
Err(_) => return Frame::err("invalid first DB index"),
};
let second = match parse_int(&args[2]) {
Ok(n) => n,
Err(_) => return Frame::err("invalid second DB index"),
};
if !ks.is_valid(first) || !ks.is_valid(second) {
return Frame::err("DB index is out of range");
}
ks.swap(first as usize, second as usize);
Frame::ok()
}