use yo_common::{Code, Error, Result};
use crate::keyspace::Keyspace;
use crate::list::{Element, List};
use crate::strings;
use crate::value::{self, Kind};
const NO_KEY: &str = "no such key";
const OUT_OF_RANGE: &str = "index out of range";
const ZERO_RANK: &str = "RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum End {
Left,
Right,
}
impl End {
#[inline]
#[must_use]
pub const fn is_left(self) -> bool {
matches!(self, End::Left)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Order {
OneByOne,
Bulk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Movem {
pub from: End,
pub to: End,
pub count: usize,
pub exactly: bool,
pub order: Order,
}
impl Keyspace {
pub fn push<'v>(
&mut self,
key: &[u8],
end: End,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<usize> {
for v in values.clone() {
strings::check_len(key, v.len())?;
}
let at = match self.list_slot(key)? {
Some(at) => at,
None => {
if values.clone().next().is_none() {
return Ok(0);
}
self.new_list(key)
}
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(at)
.expect("the record points at its body");
for v in values {
if end.is_left() {
list.push_front(v, &limits);
} else {
list.push_back(v, &limits);
}
}
Ok(list.len())
}
pub fn pushx<'v>(
&mut self,
key: &[u8],
end: End,
values: impl Iterator<Item = &'v [u8]> + Clone,
) -> Result<usize> {
if self.list_slot(key)?.is_none() {
return Ok(0);
}
self.push(key, end, values)
}
pub fn pop(&mut self, key: &[u8], end: End) -> Result<Option<Vec<u8>>> {
let Some(at) = self.list_slot(key)? else {
return Ok(None);
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(at)
.expect("the record points at its body");
let got = if end.is_left() {
list.pop_front(&limits)
} else {
list.pop_back(&limits)
};
if list.is_empty() {
self.drop_key(key);
}
Ok(got)
}
pub fn pop_into<F>(&mut self, key: &[u8], end: End, count: usize, mut f: F) -> Result<usize>
where
F: FnMut(Element<'_>),
{
let Some(at) = self.list_slot(key)? else {
return Ok(0);
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(at)
.expect("the record points at its body");
let take = count.min(list.len());
for _ in 0..take {
let e = if end.is_left() {
list.front()
} else {
list.back()
};
f(e.expect("a list shorter than it says it is"));
if end.is_left() {
list.drop_front(&limits);
} else {
list.drop_back(&limits);
}
}
if list.is_empty() {
self.drop_key(key);
}
Ok(take)
}
pub fn llen(&mut self, key: &[u8]) -> Result<usize> {
Ok(match self.list_slot(key)? {
Some(at) => self.list_at(at).len(),
None => 0,
})
}
pub fn lindex(&mut self, key: &[u8], index: i64) -> Result<Option<Element<'_>>> {
let Some(slot) = self.list_slot(key)? else {
return Ok(None);
};
let list = self.list_at(slot);
Ok(at(index, list.len()).and_then(|i| list.get(i)))
}
pub fn lrange(
&mut self,
key: &[u8],
start: i64,
stop: i64,
) -> Result<impl Iterator<Item = Element<'_>>> {
let slot = self.list_slot(key)?;
let list = slot.map(|at| self.list_at(at));
let (from, count) = match list {
Some(l) => window(start, stop, l.len()),
None => (0, 0),
};
Ok(list
.into_iter()
.flat_map(move |l| l.range(from, count))
.take(count))
}
pub fn lset(&mut self, key: &[u8], index: i64, value: &[u8]) -> Result<()> {
strings::check_len(key, value.len())?;
let Some(slot) = self.list_slot(key)? else {
return Err(Error::new(Code::Invalid, NO_KEY));
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(slot)
.expect("the record points at its body");
let Some(i) = at(index, list.len()) else {
return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
};
if !list.set(i, value, &limits) {
return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
}
Ok(())
}
pub fn linsert(&mut self, key: &[u8], before: bool, pivot: &[u8], value: &[u8]) -> Result<i64> {
strings::check_len(key, value.len())?;
let Some(slot) = self.list_slot(key)? else {
return Ok(0);
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(slot)
.expect("the record points at its body");
Ok(match list.insert_at_pivot(pivot, value, before, &limits) {
Some(len) => len as i64,
None => -1,
})
}
pub fn lrem(&mut self, key: &[u8], count: i64, value: &[u8]) -> Result<usize> {
let Some(slot) = self.list_slot(key)? else {
return Ok(0);
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(slot)
.expect("the record points at its body");
let gone = list.remove(count, value, &limits);
if list.is_empty() {
self.drop_key(key);
}
Ok(gone)
}
pub fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> Result<()> {
let Some(slot) = self.list_slot(key)? else {
return Ok(());
};
let limits = self.list_limits;
let list = self
.lists
.get_mut(slot)
.expect("the record points at its body");
let (from, count) = window(start, stop, list.len());
list.trim(from, count, &limits);
if list.is_empty() {
self.drop_key(key);
}
Ok(())
}
pub fn lpos(
&mut self,
key: &[u8],
value: &[u8],
rank: i64,
count: usize,
maxlen: usize,
out: &mut Vec<usize>,
) -> Result<()> {
out.clear();
if rank == 0 {
return Err(Error::new(Code::Invalid, ZERO_RANK));
}
self.lpos_into(key, value, rank, count, maxlen, |at| out.push(at))?;
Ok(())
}
pub fn lpos_into<F>(
&mut self,
key: &[u8],
value: &[u8],
rank: i64,
count: usize,
maxlen: usize,
mut found: F,
) -> Result<usize>
where
F: FnMut(usize),
{
if rank == 0 {
return Err(Error::new(Code::Invalid, ZERO_RANK));
}
let Some(slot) = self.list_slot(key)? else {
return Ok(0);
};
Ok(self
.list_at(slot)
.positions(value, rank, count, maxlen, &mut found))
}
pub fn lmove(&mut self, src: &[u8], dst: &[u8], from: End, to: End) -> Result<Option<&[u8]>> {
self.list_slot(dst)?;
let mut buf = std::mem::take(&mut self.scratch);
buf.clear();
let took = self.pop_into(src, from, 1, |e| e.write_to(&mut buf));
let moved = match took {
Ok(n) => n,
Err(e) => {
self.scratch = buf;
return Err(e);
}
};
if moved == 0 {
self.scratch = buf;
return Ok(None);
}
let pushed = self.push(dst, to, std::iter::once(buf.as_slice()));
self.scratch = buf;
pushed?;
Ok(Some(&self.scratch))
}
pub fn lmovem<F>(&mut self, src: &[u8], dst: &[u8], b: Movem, mut f: F) -> Result<usize>
where
F: FnMut(&[u8]),
{
self.list_slot(dst)?;
let have = self.llen(src)?;
if b.exactly && have < b.count {
return Ok(0);
}
let mut buf = std::mem::take(&mut self.scratch);
let mut ends = std::mem::take(&mut self.rows);
buf.clear();
ends.clear();
let took = self.pop_into(src, b.from, b.count, |e| {
e.write_to(&mut buf);
ends.push(buf.len());
});
let moved = match took {
Ok(n) => n,
Err(e) => {
self.scratch = buf;
self.rows = ends;
return Err(e);
}
};
if moved == 0 {
self.scratch = buf;
self.rows = ends;
return Ok(0);
}
let flip = match b.order {
Order::Bulk => !b.from.is_left(),
Order::OneByOne => b.to.is_left(),
};
let at = |i: usize| {
let end = ends[i];
let start = if i == 0 { 0 } else { ends[i - 1] };
&buf[start..end]
};
let placed = |i: usize| if flip { moved - 1 - i } else { i };
let sent = |i: usize| placed(if b.to.is_left() { moved - 1 - i } else { i });
let pushed = self.push(dst, b.to, (0..moved).map(|i| at(sent(i))));
if pushed.is_ok() {
for i in 0..moved {
f(at(placed(i)));
}
}
self.scratch = buf;
self.rows = ends;
pushed?;
Ok(moved)
}
#[inline]
fn list_slot(&mut self, key: &[u8]) -> Result<Option<u32>> {
self.live_slot(key, Kind::List)
}
#[inline]
fn list_at(&self, at: u32) -> &List {
self.lists.get(at).expect("the record points at its body")
}
fn new_list(&mut self, key: &[u8]) -> u32 {
let at = yo_alloc::first_touch(|| self.lists.insert(List::new()));
let len = value::slot_record_len(false);
self.write_rec(key, len, |out| {
value::write_slot_record(out, Kind::List, at, None);
});
self.bodies += 1;
at
}
}
#[inline]
fn at(index: i64, len: usize) -> Option<usize> {
let i = if index < 0 { len as i64 + index } else { index };
(i >= 0 && (i as usize) < len).then_some(i as usize)
}
#[inline]
fn window(start: i64, stop: i64, len: usize) -> (usize, usize) {
let n = len as i64;
let from = if start < 0 { (n + start).max(0) } else { start };
let to = if stop < 0 { n + stop } else { stop.min(n - 1) };
if from > to || from >= n || to < 0 {
return (0, 0);
}
(from as usize, (to - from + 1) as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Clock;
use crate::list::Encoding;
use yo_common::Code;
fn db() -> Keyspace {
Keyspace::with_clock(Clock::fixed(1_000))
}
fn rpush(d: &mut Keyspace, key: &[u8], values: &[&[u8]]) -> usize {
d.push(key, End::Right, values.iter().copied())
.expect("a list")
}
fn all(d: &mut Keyspace, key: &[u8]) -> Vec<String> {
d.lrange(key, 0, -1)
.expect("a list")
.map(|e| String::from_utf8(e.to_vec()).expect("utf8 in these tests"))
.collect()
}
#[test]
fn pushing_to_a_key_that_is_not_there_makes_it() {
let mut d = db();
assert_eq!(rpush(&mut d, b"l", &[b"a", b"b", b"c"]), 3);
assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
assert_eq!(d.llen(b"l").expect("a list"), 3);
}
#[test]
fn lpush_puts_the_last_element_in_front() {
let mut d = db();
d.push(b"l", End::Left, [b"a".as_slice(), b"b", b"c"].into_iter())
.expect("a list");
assert_eq!(all(&mut d, b"l"), ["c", "b", "a"]);
}
#[test]
fn pushing_nothing_does_not_make_a_key() {
let mut d = db();
let none: [&[u8]; 0] = [];
assert_eq!(d.push(b"l", End::Left, none.into_iter()).expect("ok"), 0);
assert_eq!(d.kind_of(b"l"), None);
}
#[test]
fn pushx_only_pushes_to_a_list_that_is_there() {
let mut d = db();
assert_eq!(
d.pushx(b"l", End::Right, [b"a".as_slice()].into_iter())
.expect("ok"),
0
);
assert_eq!(d.kind_of(b"l"), None);
rpush(&mut d, b"l", &[b"a"]);
assert_eq!(
d.pushx(b"l", End::Right, [b"b".as_slice()].into_iter())
.expect("ok"),
2
);
}
#[test]
fn popping_the_last_element_takes_the_key_with_it() {
let mut d = db();
rpush(&mut d, b"l", &[b"only"]);
assert_eq!(
d.pop(b"l", End::Left).expect("ok").as_deref(),
Some(&b"only"[..])
);
assert_eq!(d.kind_of(b"l"), None);
assert_eq!(d.pop(b"l", End::Left).expect("ok"), None);
}
#[test]
fn a_count_pop_takes_from_the_end_it_was_asked_for() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c", b"d"]);
let mut got = Vec::new();
d.pop_into(b"l", End::Right, 2, |e| got.push(e.to_vec()))
.expect("a list");
assert_eq!(got, [b"d".to_vec(), b"c".to_vec()]);
assert_eq!(all(&mut d, b"l"), ["a", "b"]);
}
#[test]
fn a_count_pop_larger_than_the_list_empties_it() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b"]);
let mut n = 0;
assert_eq!(
d.pop_into(b"l", End::Left, 99, |_| n += 1).expect("a list"),
2
);
assert_eq!(n, 2);
assert_eq!(d.kind_of(b"l"), None);
}
#[test]
fn lrange_clamps_at_both_ends() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
let got: Vec<_> = d
.lrange(b"l", -100, 100)
.expect("a list")
.map(|e| e.to_vec())
.collect();
assert_eq!(got.len(), 3);
assert_eq!(d.lrange(b"l", 2, 1).expect("a list").count(), 0);
assert_eq!(d.lrange(b"l", 5, 9).expect("a list").count(), 0);
assert_eq!(d.lrange(b"nope", 0, -1).expect("no key").count(), 0);
}
#[test]
fn lindex_counts_from_the_back_when_it_is_negative() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
assert_eq!(
d.lindex(b"l", 0).expect("ok").map(|e| e.to_vec()),
Some(b"a".to_vec())
);
assert_eq!(
d.lindex(b"l", -1).expect("ok").map(|e| e.to_vec()),
Some(b"c".to_vec())
);
assert!(d.lindex(b"l", 3).expect("ok").is_none());
assert!(d.lindex(b"l", -4).expect("ok").is_none());
assert!(d.lindex(b"nope", 0).expect("ok").is_none());
}
#[test]
fn lset_says_which_of_the_two_ways_it_missed() {
let mut d = db();
let e = d.lset(b"nope", 0, b"x").expect_err("no key");
assert_eq!(e.message(), NO_KEY);
rpush(&mut d, b"l", &[b"a", b"b"]);
d.lset(b"l", -1, b"z").expect("in range");
assert_eq!(all(&mut d, b"l"), ["a", "z"]);
let e = d.lset(b"l", 9, b"x").expect_err("out of range");
assert_eq!(e.message(), OUT_OF_RANGE);
}
#[test]
fn linsert_has_three_answers() {
let mut d = db();
assert_eq!(d.linsert(b"nope", true, b"a", b"x").expect("ok"), 0);
rpush(&mut d, b"l", &[b"a", b"c"]);
assert_eq!(d.linsert(b"l", true, b"c", b"b").expect("ok"), 3);
assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
assert_eq!(d.linsert(b"l", false, b"zz", b"x").expect("ok"), -1);
}
#[test]
fn lrem_counts_from_the_end_the_sign_says() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"x", b"b", b"x", b"c", b"x"]);
assert_eq!(d.lrem(b"l", 1, b"x").expect("ok"), 1);
assert_eq!(all(&mut d, b"l"), ["a", "b", "x", "c", "x"]);
assert_eq!(d.lrem(b"l", -1, b"x").expect("ok"), 1);
assert_eq!(all(&mut d, b"l"), ["a", "b", "x", "c"]);
assert_eq!(d.lrem(b"l", 0, b"x").expect("ok"), 1);
assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
}
#[test]
fn removing_everything_takes_the_key() {
let mut d = db();
rpush(&mut d, b"l", &[b"x", b"x"]);
assert_eq!(d.lrem(b"l", 0, b"x").expect("ok"), 2);
assert_eq!(d.kind_of(b"l"), None);
}
#[test]
fn ltrim_keeps_the_window() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c", b"d", b"e"]);
d.ltrim(b"l", 1, -2).expect("ok");
assert_eq!(all(&mut d, b"l"), ["b", "c", "d"]);
}
#[test]
fn an_empty_window_deletes_the_key() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b"]);
d.ltrim(b"l", 1, 0).expect("ok");
assert_eq!(d.kind_of(b"l"), None);
d.ltrim(b"nope", 0, -1).expect("no key is not an error");
}
#[test]
fn lpos_answers_where_and_how_many() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c", b"b", b"b"]);
let mut out = Vec::new();
d.lpos(b"l", b"b", 1, 0, 0, &mut out).expect("ok");
assert_eq!(out, [1, 3, 4]);
d.lpos(b"l", b"b", -1, 2, 0, &mut out).expect("ok");
assert_eq!(out, [4, 3]);
d.lpos(b"l", b"b", 1, 0, 2, &mut out).expect("ok");
assert_eq!(out, [1]);
d.lpos(b"l", b"zz", 1, 0, 0, &mut out).expect("ok");
assert!(out.is_empty());
d.lpos(b"nope", b"b", 1, 0, 0, &mut out).expect("ok");
assert!(out.is_empty());
}
#[test]
fn a_rank_of_zero_is_an_error() {
let mut d = db();
rpush(&mut d, b"l", &[b"a"]);
let e = d
.lpos(b"l", b"a", 0, 0, 0, &mut Vec::new())
.expect_err("zero");
assert_eq!(e.message(), ZERO_RANK);
}
#[test]
fn lmovem_orders_the_block_four_ways() {
use End::{Left, Right};
use Order::{Bulk, OneByOne};
for (from, to, order, want, left) in [
(Left, Right, OneByOne, ["a", "b"], ["c", "d", "e"]),
(Left, Right, Bulk, ["a", "b"], ["c", "d", "e"]),
(Left, Left, OneByOne, ["b", "a"], ["c", "d", "e"]),
(Left, Left, Bulk, ["a", "b"], ["c", "d", "e"]),
(Right, Left, OneByOne, ["d", "e"], ["a", "b", "c"]),
(Right, Left, Bulk, ["d", "e"], ["a", "b", "c"]),
(Right, Right, OneByOne, ["e", "d"], ["a", "b", "c"]),
(Right, Right, Bulk, ["d", "e"], ["a", "b", "c"]),
] {
let mut d = db();
rpush(&mut d, b"s", &[b"a", b"b", b"c", b"d", b"e"]);
let mut got = Vec::new();
let b = Movem {
from,
to,
count: 2,
exactly: false,
order,
};
let n = d
.lmovem(b"s", b"t", b, |v| {
got.push(String::from_utf8(v.to_vec()).expect("utf8 in these tests"));
})
.expect("two lists");
let how = format!("{from:?} {to:?} {order:?}");
assert_eq!(n, 2, "{how}");
assert_eq!(got, want, "the reply for {how}");
assert_eq!(all(&mut d, b"t"), want, "the destination for {how}");
assert_eq!(all(&mut d, b"s"), left, "what is left for {how}");
}
}
fn block(from: End, to: End, count: usize, exactly: bool) -> Movem {
Movem {
from,
to,
count,
exactly,
order: Order::Bulk,
}
}
#[test]
fn lmovem_exactly_takes_all_of_them_or_none() {
let mut d = db();
rpush(&mut d, b"s", &[b"a", b"b", b"c"]);
let all_of_four = block(End::Left, End::Right, 4, true);
let n = d
.lmovem(b"s", b"t", all_of_four, |_| {
panic!("nothing should have moved")
})
.expect("two lists");
assert_eq!(n, 0);
assert_eq!(
all(&mut d, b"s"),
["a", "b", "c"],
"the source is untouched"
);
assert_eq!(d.llen(b"t").expect("a list"), 0, "and nothing was made");
let mut got = Vec::new();
let up_to_four = block(End::Left, End::Right, 4, false);
let n = d
.lmovem(b"s", b"t", up_to_four, |v| got.push(v.to_vec()))
.expect("two lists");
assert_eq!(n, 3);
assert_eq!(got.len(), 3);
assert!(!d.exists(b"s"), "an emptied source goes");
}
#[test]
fn lmovem_onto_itself_rotates_by_the_count() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
d.lmovem(b"l", b"l", block(End::Left, End::Right, 2, false), |_| {})
.expect("a list");
assert_eq!(all(&mut d, b"l"), ["c", "a", "b"]);
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
d.lmovem(b"l", b"l", block(End::Right, End::Left, 2, false), |_| {})
.expect("a list");
assert_eq!(all(&mut d, b"l"), ["b", "c", "a"]);
}
#[test]
fn lmovem_checks_the_destination_before_taking_anything() {
let mut d = db();
rpush(&mut d, b"s", &[b"a", b"b"]);
d.set_plain(b"str", b"v").expect("room");
let two = block(End::Left, End::Right, 2, false);
let e = d
.lmovem(b"s", b"str", two, |_| {})
.expect_err("the destination is a string");
assert_eq!(e.code(), Code::WrongType);
assert_eq!(all(&mut d, b"s"), ["a", "b"], "the source is untouched");
let n = d
.lmovem(b"nope", b"t", two, |_| {})
.expect("a source that is not there is not an error");
assert_eq!(n, 0);
}
#[test]
fn lmove_between_two_keys_makes_the_second_one() {
let mut d = db();
rpush(&mut d, b"src", &[b"a", b"b"]);
let got = d.lmove(b"src", b"dst", End::Right, End::Left).expect("ok");
assert_eq!(got, Some(&b"b"[..]));
assert_eq!(all(&mut d, b"src"), ["a"]);
assert_eq!(all(&mut d, b"dst"), ["b"]);
}
#[test]
fn lmove_onto_itself_rotates() {
let mut d = db();
rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
d.lmove(b"l", b"l", End::Right, End::Left).expect("ok");
assert_eq!(all(&mut d, b"l"), ["c", "a", "b"]);
d.lmove(b"l", b"l", End::Left, End::Right).expect("ok");
assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
}
#[test]
fn lmove_from_a_key_that_is_not_there_does_nothing() {
let mut d = db();
assert_eq!(
d.lmove(b"nope", b"dst", End::Left, End::Left).expect("ok"),
None
);
assert_eq!(d.kind_of(b"dst"), None);
}
#[test]
fn lmove_stops_allocating_once_its_buffer_is_grown() {
let mut d = db();
rpush(&mut d, b"q", &[b"a", b"b", b"c"]);
d.lmove(b"q", b"q", End::Right, End::Left).expect("ok");
let (_, allocs) = crate::tally::counted(|| {
for _ in 0..100 {
d.lmove(b"q", b"q", End::Right, End::Left).expect("ok");
}
});
assert_eq!(allocs, 0, "lmove allocated {allocs} times in a hundred");
assert_eq!(all(&mut d, b"q"), ["b", "c", "a"]);
}
#[test]
fn lrem_does_not_allocate_to_remove_a_handful() {
let mut d = db();
let many: Vec<&[u8]> = (0..100).map(|_| b"gone".as_slice()).collect();
rpush(&mut d, b"l", &many);
rpush(&mut d, b"l", &[b"keep"]);
let (_, allocs) = crate::tally::counted(|| {
for _ in 0..100 {
assert_eq!(d.lrem(b"l", 1, b"gone").expect("a list"), 1);
}
});
assert_eq!(allocs, 0, "lrem allocated {allocs} times in a hundred");
assert_eq!(all(&mut d, b"l"), ["keep"]);
}
#[test]
fn lrem_with_more_hits_than_fit_inline_still_removes_all_of_them() {
let mut d = db();
let many: Vec<&[u8]> = (0..40).map(|_| b"x".as_slice()).collect();
rpush(&mut d, b"l", &many);
rpush(&mut d, b"l", &[b"keep"]);
assert_eq!(d.lrem(b"l", 0, b"x").expect("a list"), 40);
assert_eq!(all(&mut d, b"l"), ["keep"]);
}
#[test]
fn lmove_checks_the_destination_before_taking_anything() {
let mut d = db();
rpush(&mut d, b"src", &[b"a"]);
d.set_plain(b"dst", b"a string").expect("room");
let e = d
.lmove(b"src", b"dst", End::Left, End::Left)
.expect_err("the destination is a string");
assert_eq!(e.code(), Code::WrongType);
assert_eq!(all(&mut d, b"src"), ["a"]);
}
#[test]
fn every_command_says_wrongtype_against_a_string() {
let mut d = db();
d.set_plain(b"s", b"a string").expect("room");
assert_eq!(
d.push(b"s", End::Left, [b"x".as_slice()].into_iter())
.expect_err("a string")
.code(),
Code::WrongType
);
assert_eq!(d.llen(b"s").expect_err("a string").code(), Code::WrongType);
assert_eq!(
d.pop(b"s", End::Left).expect_err("a string").code(),
Code::WrongType
);
assert_eq!(
d.lset(b"s", 0, b"x").expect_err("a string").code(),
Code::WrongType
);
assert_eq!(
d.ltrim(b"s", 0, -1).expect_err("a string").code(),
Code::WrongType
);
}
#[test]
fn a_list_is_a_list_to_the_rest_of_the_keyspace() {
let mut d = db();
rpush(&mut d, b"l", &[b"a"]);
assert_eq!(d.kind_of(b"l").map(|k| k.name()), Some("list"));
assert_eq!(d.encoding_name(b"l"), Some(Encoding::Listpack.name()));
assert!(d.exists(b"l"));
assert!(d.drop_key(b"l"));
assert_eq!(d.kind_of(b"l"), None);
}
#[test]
fn a_list_can_be_given_a_deadline_and_reaped() {
let mut d = Keyspace::with_clock(Clock::fixed(1_000));
rpush(&mut d, b"l", &[b"a", b"b"]);
assert!(d.set_expiry(b"l", Some(1_500)));
assert_eq!(all(&mut d, b"l"), ["a", "b"]);
d.clock_mut().advance(1_000);
assert_eq!(d.llen(b"l").expect("gone"), 0);
assert_eq!(d.kind_of(b"l"), None);
}
#[test]
fn a_big_list_is_chunked_and_still_answers_the_same() {
let mut d = db();
let value = vec![b'x'; 400];
for i in 0..40 {
let mut v = value.clone();
v.extend_from_slice(format!("{i}").as_bytes());
d.push(b"l", End::Right, [v.as_slice()].into_iter())
.expect("a list");
}
assert_eq!(d.encoding_name(b"l"), Some(Encoding::Quicklist.name()));
assert_eq!(d.llen(b"l").expect("a list"), 40);
let last = d.lindex(b"l", -1).expect("ok").expect("in range").to_vec();
assert!(last.ends_with(b"39"));
d.ltrim(b"l", 0, 0).expect("ok");
assert_eq!(d.llen(b"l").expect("a list"), 1);
assert_eq!(d.encoding_name(b"l"), Some(Encoding::Listpack.name()));
}
}