fast-cache 0.1.0

Embedded-first thread-per-core in-memory cache with optional Redis-compatible server
Documentation
use super::helpers::{normalize_index, normalize_range};
use super::*;

impl RedisObjectBucket {
    #[inline(always)]
    pub(crate) fn push_list_existing_or_wrongtype_hashed(
        &mut self,
        key_hash: u64,
        key: &[u8],
        values: &[&[u8]],
        front: bool,
    ) -> RedisObjectWriteAttempt {
        if values.is_empty() {
            return RedisObjectWriteAttempt::Complete(RedisObjectResult::Integer(0));
        }
        if let Some(slot) = self.lists.get_hashed(key_hash, key) {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            list.push_values(values, front);
            return RedisObjectWriteAttempt::Complete(
                RedisObjectResult::Integer(list.len() as i64),
            );
        }
        if self.has_non_list(key) {
            RedisObjectWriteAttempt::Complete(RedisObjectResult::WrongType)
        } else {
            RedisObjectWriteAttempt::Missing
        }
    }

    #[inline(always)]
    pub(crate) fn push_list_new_unchecked_hashed(
        &mut self,
        key_hash: u64,
        key: &[u8],
        values: &[&[u8]],
        front: bool,
    ) -> RedisObjectResult {
        let list = ListObject::from_values(values, front);
        let slot = self.list_slab.insert(list);
        self.lists.insert_hashed(key_hash, key.to_vec(), slot);
        RedisObjectResult::Integer(values.len() as i64)
    }

    pub(crate) fn push_list_existing(
        &mut self,
        key: &[u8],
        values: &[&[u8]],
        front: bool,
    ) -> (RedisObjectResult, bool) {
        if values.is_empty() {
            return (RedisObjectResult::Integer(0), false);
        }
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            list.push_values(values, front);
            return (RedisObjectResult::Integer(list.len() as i64), false);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Integer(0), false)
        }
    }

    pub(crate) fn pop_list(&mut self, key: &[u8], front: bool) -> (RedisObjectResult, bool) {
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let value = if front {
                list.pop_front()
            } else {
                list.pop_back()
            };
            let empty = list.is_empty();
            if empty {
                self.lists.remove(key);
                self.list_slab.remove(slot);
                self.expire_at_ms.remove(key);
            }
            return (RedisObjectResult::Bulk(value), empty);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Bulk(None), false)
        }
    }

    pub(crate) fn pop_list_count(
        &mut self,
        key: &[u8],
        count: usize,
        front: bool,
    ) -> (RedisObjectResult, bool) {
        if count == 0 {
            return (RedisObjectResult::Array(Vec::new()), false);
        }
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let mut values = Vec::with_capacity(count.min(list.len()));
            for _ in 0..count {
                let value = if front {
                    list.pop_front()
                } else {
                    list.pop_back()
                };
                let Some(value) = value else { break };
                values.push(Some(value));
            }
            let empty = list.is_empty();
            if empty {
                self.lists.remove(key);
                self.list_slab.remove(slot);
                self.expire_at_ms.remove(key);
            }
            return (RedisObjectResult::Array(values), empty);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Bulk(None), false)
        }
    }

    pub(crate) fn llen(&self, key: &[u8]) -> RedisObjectResult {
        match self.lists.get(key).copied() {
            Some(slot) => RedisObjectResult::Integer(
                self.list_slab
                    .get(slot)
                    .expect("list slab slot missing")
                    .len() as i64,
            ),
            None if self.has_non_list(key) => RedisObjectResult::WrongType,
            None => RedisObjectResult::Integer(0),
        }
    }

    pub(crate) fn llen_visit(&self, key: &[u8], write: impl FnOnce(i64)) -> RedisObjectReadOutcome {
        match self.lists.get(key).copied() {
            Some(slot) => {
                write(
                    self.list_slab
                        .get(slot)
                        .expect("list slab slot missing")
                        .len() as i64,
                );
                RedisObjectReadOutcome::Written
            }
            None if self.has_non_list(key) => RedisObjectReadOutcome::WrongType,
            None => RedisObjectReadOutcome::Missing,
        }
    }

    pub(crate) fn lindex(&self, key: &[u8], index: i64) -> RedisObjectResult {
        match self.lists.get(key).copied() {
            Some(slot) => {
                let list = self.list_slab.get(slot).expect("list slab slot missing");
                let Some(index) = normalize_index(index, list.len()) else {
                    return RedisObjectResult::Bulk(None);
                };
                RedisObjectResult::Bulk(list.get(index).cloned())
            }
            None if self.has_non_list(key) => RedisObjectResult::WrongType,
            None => RedisObjectResult::Bulk(None),
        }
    }

    pub(crate) fn lindex_visit(
        &self,
        key: &[u8],
        index: i64,
        write: impl FnOnce(Option<&[u8]>),
    ) -> RedisObjectReadOutcome {
        match self.lists.get(key).copied() {
            Some(slot) => {
                let list = self.list_slab.get(slot).expect("list slab slot missing");
                let value = normalize_index(index, list.len())
                    .and_then(|index| list.get(index))
                    .map(Vec::as_slice);
                write(value);
                RedisObjectReadOutcome::Written
            }
            None if self.has_non_list(key) => RedisObjectReadOutcome::WrongType,
            None => RedisObjectReadOutcome::Missing,
        }
    }

    pub(crate) fn lrange(&self, key: &[u8], start: i64, stop: i64) -> RedisObjectResult {
        match self.lists.get(key).copied() {
            Some(slot) => {
                let list = self.list_slab.get(slot).expect("list slab slot missing");
                let Some((start, stop)) = normalize_range(start, stop, list.len()) else {
                    return RedisObjectResult::Array(Vec::new());
                };
                RedisObjectResult::Array(
                    list.iter()
                        .skip(start)
                        .take(stop - start + 1)
                        .cloned()
                        .map(Some)
                        .collect(),
                )
            }
            None if self.has_non_list(key) => RedisObjectResult::WrongType,
            None => RedisObjectResult::Array(Vec::new()),
        }
    }

    pub(crate) fn lset(
        &mut self,
        key: &[u8],
        index: i64,
        value: &[u8],
    ) -> (RedisObjectResult, bool) {
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let Some(index) = normalize_index(index, list.len()) else {
                return (RedisObjectResult::Simple("ERR index out of range"), false);
            };
            list.set(index, value.to_vec());
            return (RedisObjectResult::Simple("OK"), false);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Simple("ERR no such key"), false)
        }
    }

    pub(crate) fn lrem(
        &mut self,
        key: &[u8],
        count: i64,
        value: &[u8],
    ) -> (RedisObjectResult, bool) {
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let mut removed = 0usize;
            if count >= 0 {
                let limit = if count == 0 {
                    usize::MAX
                } else {
                    count as usize
                };
                let mut index = 0usize;
                while index < list.len() && removed < limit {
                    if list.get(index).is_some_and(|item| item.as_slice() == value) {
                        list.remove(index);
                        removed += 1;
                    } else {
                        index += 1;
                    }
                }
            } else {
                let limit = count.unsigned_abs() as usize;
                let mut index = list.len();
                while index > 0 && removed < limit {
                    index -= 1;
                    if list.get(index).is_some_and(|item| item.as_slice() == value) {
                        list.remove(index);
                        removed += 1;
                    }
                }
            }
            let empty = list.is_empty();
            if empty {
                self.lists.remove(key);
                self.list_slab.remove(slot);
                self.expire_at_ms.remove(key);
            }
            return (RedisObjectResult::Integer(removed as i64), empty);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Integer(0), false)
        }
    }

    pub(crate) fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> (RedisObjectResult, bool) {
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let Some((start, stop)) = normalize_range(start, stop, list.len()) else {
                list.clear();
                self.lists.remove(key);
                self.list_slab.remove(slot);
                self.expire_at_ms.remove(key);
                return (RedisObjectResult::Simple("OK"), true);
            };
            let keep = stop - start + 1;
            for _ in 0..start {
                list.pop_front();
            }
            while list.len() > keep {
                list.pop_back();
            }
            let empty = list.is_empty();
            if empty {
                self.lists.remove(key);
                self.list_slab.remove(slot);
                self.expire_at_ms.remove(key);
            }
            return (RedisObjectResult::Simple("OK"), empty);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Simple("OK"), false)
        }
    }

    pub(crate) fn linsert(
        &mut self,
        key: &[u8],
        before: bool,
        pivot: &[u8],
        value: &[u8],
    ) -> (RedisObjectResult, bool) {
        if let Some(slot) = self.lists.get(key).copied() {
            let list = self
                .list_slab
                .get_mut(slot)
                .expect("list slab slot missing");
            let Some(index) = list.iter().position(|item| item.as_slice() == pivot) else {
                return (RedisObjectResult::Integer(-1), false);
            };
            let index = if before { index } else { index + 1 };
            list.insert(index, value.to_vec());
            return (RedisObjectResult::Integer(list.len() as i64), false);
        }
        if self.has_non_list(key) {
            (RedisObjectResult::WrongType, false)
        } else {
            (RedisObjectResult::Integer(0), false)
        }
    }

    pub(crate) fn lrange_visit(
        &self,
        key: &[u8],
        start: i64,
        stop: i64,
        mut emit: impl FnMut(RedisObjectArrayItem<'_>),
    ) -> RedisObjectReadOutcome {
        match self.lists.get(key).copied() {
            Some(slot) => {
                let list = self.list_slab.get(slot).expect("list slab slot missing");
                let Some((start, stop)) = normalize_range(start, stop, list.len()) else {
                    emit(RedisObjectArrayItem::Begin(0));
                    return RedisObjectReadOutcome::Written;
                };
                let count = stop - start + 1;
                emit(RedisObjectArrayItem::Begin(count));
                for value in list.iter().skip(start).take(count) {
                    emit(RedisObjectArrayItem::Bulk(Some(value.as_slice())));
                }
                RedisObjectReadOutcome::Written
            }
            None if self.has_non_list(key) => RedisObjectReadOutcome::WrongType,
            None => RedisObjectReadOutcome::Missing,
        }
    }
}