wedb_embed 0.1.0

Embedded Kvrocks-compatible storage engine for WeDb
Documentation
pub use crate::meta::KeyMeta as StringMeta;
use crate::meta::{KeyMeta, RedisType};

/// 单 KV 字符串头部大小(1字节flags + 8字节过期时间,对标 Apache Kvrocks SingleKV 9字节头)
pub const STRING_HDR_SIZE: usize = KeyMeta::KVROCKS_SINGLE_KV_ENCODED_SIZE;

/// 编码 SingleKV 字符串值(9 字节元数据头 + 载荷,对标 Apache Kvrocks Metadata::Encode + payload)
#[inline]
pub fn encode_string_value(value: &[u8], expire_at_ms: u64) -> Vec<u8> {
    let flags = KeyMeta::META_64BIT_ENCODING_MASK | (RedisType::String as u8);
    let mut out = Vec::with_capacity(STRING_HDR_SIZE + value.len());
    out.push(flags);
    out.extend_from_slice(&expire_at_ms.to_be_bytes());
    out.extend_from_slice(value);
    out
}

/// 解码 SingleKV 字符串值,自适应支持 Kvrocks 64位 9字节紧凑头、32位 5字节传统头与裸字节
#[inline]
pub fn decode_string_value(raw: &[u8]) -> (u64, &[u8]) {
    if raw.len() >= STRING_HDR_SIZE
        && (raw[0] & KeyMeta::META_64BIT_ENCODING_MASK != 0)
        && (raw[0] & KeyMeta::META_TYPE_MASK == RedisType::String as u8)
    {
        let expire_at = u64::from_be_bytes(raw[1..STRING_HDR_SIZE].try_into().unwrap_or_default());
        (expire_at, &raw[STRING_HDR_SIZE..])
    } else if raw.len() >= 5 && raw[0] == RedisType::String as u8 {
        // 兼容 Kvrocks 32位秒级编码
        let expire_sec = u32::from_be_bytes(raw[1..5].try_into().unwrap_or_default()) as u64;
        let expire_ms = if expire_sec > 0 { expire_sec * 1000 } else { 0 };
        (expire_ms, &raw[5..])
    } else {
        (0, raw)
    }
}

/// 检查字符串是否已过期
#[inline]
pub const fn is_string_expired(expire_at_ms: u64, now_ms: u64) -> bool {
    expire_at_ms > 0 && expire_at_ms <= now_ms
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string_value_roundtrip() {
        let val = b"hello string";
        let expire = 1_850_000_000_000;
        let enc = encode_string_value(val, expire);
        assert_eq!(enc.len(), STRING_HDR_SIZE + val.len());
        assert_eq!(enc[0], 0x81);

        let (dec_exp, dec_val) = decode_string_value(&enc);
        assert_eq!(dec_exp, expire);
        assert_eq!(dec_val, val);

        let enc_raw = encode_string_value(val, 0);
        assert_eq!(enc_raw.len(), STRING_HDR_SIZE + val.len());
        assert_eq!(enc_raw[0], 0x81);
        let (dec_exp0, dec_val0) = decode_string_value(&enc_raw);
        assert_eq!(dec_exp0, 0);
        assert_eq!(dec_val0, val);
    }

    #[test]
    fn test_string_legacy_32bit_decode() {
        let val = b"legacy value";
        let mut legacy = vec![0x01]; // 32-bit String type
        legacy.extend_from_slice(&12345u32.to_be_bytes()); // 12345 sec
        legacy.extend_from_slice(val);

        let (dec_exp, dec_val) = decode_string_value(&legacy);
        assert_eq!(dec_exp, 12345000);
        assert_eq!(dec_val, val);
    }
}