wedb_embed 0.1.1

Embedded database engine providing Redis-like APIs, built on fjall / 嵌入式数据库引擎,提供类似 Redis 的接口,底层基于 fjall 开发
Documentation
use crate::key_composer::KeyTag;
use crate::meta::{KeyMeta, RedisType};

/// 列表结构元数据(对标 Apache Kvrocks ListMetadata 42字节 / 紧凑41字节)
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub struct ListMeta {
    pub base: KeyMeta,
    pub head: u64,
    pub tail: u64,
}

impl ListMeta {
    pub const INITIAL_INDEX: u64 = u64::MAX / 2; // 0x7fff_ffff_ffff_ffff
    pub const ENCODED_SIZE: usize = KeyMeta::ENCODED_SIZE + 16; // 26 + 16 = 42
    pub const KVROCKS_ENCODED_SIZE: usize = KeyMeta::KVROCKS_COMPLEX_ENCODED_SIZE + 16; // 25 + 16 = 41

    #[inline]
    pub const fn new(expire_at: u64, version: u64) -> Self {
        Self {
            base: KeyMeta::new(RedisType::List, expire_at, version, 0),
            head: Self::INITIAL_INDEX,
            tail: Self::INITIAL_INDEX,
        }
    }

    #[inline]
    pub fn new_with_version(expire_at: u64) -> Self {
        Self {
            base: KeyMeta::new_with_version(RedisType::List, expire_at, 0),
            head: Self::INITIAL_INDEX,
            tail: Self::INITIAL_INDEX,
        }
    }

    #[inline]
    pub const fn size(&self) -> u64 {
        self.base.size
    }

    #[inline]
    pub const fn version(&self) -> u64 {
        self.base.version
    }

    #[inline]
    pub const fn expire_at(&self) -> u64 {
        self.base.expire_at
    }

    #[inline]
    pub const fn ttl(&self, now_ms: u64) -> i64 {
        self.base.ttl(now_ms)
    }

    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.base.size == 0
    }

    #[inline]
    pub const fn is_expired(&self, now_ms: u64) -> bool {
        self.base.is_expired(now_ms)
    }

    #[inline]
    pub fn encode(&self) -> [u8; Self::ENCODED_SIZE] {
        let mut buf = [0u8; Self::ENCODED_SIZE];
        buf[..KeyMeta::ENCODED_SIZE].copy_from_slice(&self.base.encode());
        buf[KeyMeta::ENCODED_SIZE..KeyMeta::ENCODED_SIZE + 8]
            .copy_from_slice(&self.head.to_be_bytes());
        buf[KeyMeta::ENCODED_SIZE + 8..Self::ENCODED_SIZE]
            .copy_from_slice(&self.tail.to_be_bytes());
        buf
    }

    /// 编码为 Kvrocks 1:1 紧凑 41 字节格式(零堆内存分配)
    #[inline]
    pub fn encode_kvrocks(&self) -> [u8; Self::KVROCKS_ENCODED_SIZE] {
        let mut buf = [0u8; Self::KVROCKS_ENCODED_SIZE];
        let flags =
            KeyMeta::META_64BIT_ENCODING_MASK | (RedisType::List as u8 & KeyMeta::META_TYPE_MASK);
        buf[0] = flags;
        buf[1..9].copy_from_slice(&self.base.expire_at.to_be_bytes());
        buf[9..17].copy_from_slice(&self.base.version.to_be_bytes());
        buf[17..25].copy_from_slice(&self.base.size.to_be_bytes());
        buf[25..33].copy_from_slice(&self.head.to_be_bytes());
        buf[33..41].copy_from_slice(&self.tail.to_be_bytes());
        buf
    }

    #[inline]
    pub fn decode(bytes: &[u8]) -> Option<Self> {
        if bytes.len() >= Self::ENCODED_SIZE
            && bytes[0] <= 14
            && (bytes[1] == 0 || bytes[1] == 0x80)
        {
            let base = KeyMeta::decode(&bytes[..KeyMeta::ENCODED_SIZE])?;
            let head = read_u64_be(bytes, KeyMeta::ENCODED_SIZE)?;
            let tail = read_u64_be(bytes, KeyMeta::ENCODED_SIZE + 8)?;
            Some(Self { base, head, tail })
        } else if bytes.len() >= Self::KVROCKS_ENCODED_SIZE
            && (bytes[0] & KeyMeta::META_64BIT_ENCODING_MASK != 0)
        {
            let base = KeyMeta::decode(&bytes[..KeyMeta::KVROCKS_COMPLEX_ENCODED_SIZE])?;
            let head = read_u64_be(bytes, KeyMeta::KVROCKS_COMPLEX_ENCODED_SIZE)?;
            let tail = read_u64_be(bytes, KeyMeta::KVROCKS_COMPLEX_ENCODED_SIZE + 8)?;
            Some(Self { base, head, tail })
        } else if bytes.len() >= 33 && (bytes[0] & KeyMeta::META_64BIT_ENCODING_MASK == 0) {
            // Kvrocks 32-bit 紧凑格式 (17字节 base + 16字节 head/tail)
            let base = KeyMeta::decode(&bytes[..17])?;
            let head = read_u64_be(bytes, 17)?;
            let tail = read_u64_be(bytes, 25)?;
            Some(Self { base, head, tail })
        } else if bytes.len() == 16 {
            let head = read_u64_be(bytes, 0)?;
            let tail = read_u64_be(bytes, 8)?;
            let size = if tail >= head {
                tail.wrapping_sub(head)
            } else {
                0
            };
            Some(Self {
                base: KeyMeta::new(RedisType::List, 0, 0, size),
                head,
                tail,
            })
        } else {
            None
        }
    }
}

#[inline(always)]
fn read_u64_be(bytes: &[u8], offset: usize) -> Option<u64> {
    let s: &[u8; 8] = bytes.get(offset..offset + 8)?.try_into().ok()?;
    Some(u64::from_be_bytes(*s))
}

impl_meta_ops!(ListMeta, KeyTag::ListMeta.as_slice());