wedb_embed 0.1.1

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

/// ZADD 选项标志(对标 Apache Kvrocks ZSetFlags)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZAdd {
    Nx,
    Xx,
    Gt,
    Lt,
    Ch,
    Incr,
}

/// 聚合函数类型(对标 Apache Kvrocks AggregateMethod)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Aggregate {
    #[default]
    Sum,
    Min,
    Max,
}

impl Aggregate {
    #[inline]
    pub fn parse(s: &str) -> Self {
        if s.eq_ignore_ascii_case("MIN") {
            Self::Min
        } else if s.eq_ignore_ascii_case("MAX") {
            Self::Max
        } else {
            Self::Sum
        }
    }

    #[inline]
    pub fn apply(&self, current: f64, new_val: f64) -> f64 {
        let res = match self {
            Self::Sum => current + new_val,
            Self::Min => current.min(new_val),
            Self::Max => current.max(new_val),
        };
        if res.is_nan() { 0.0 } else { res }
    }
}

/// 分数范围规格(对标 Apache Kvrocks RangeScoreSpec)
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RangeScoreSpec {
    pub min: f64,
    pub max: f64,
    pub minex: bool,
    pub maxex: bool,
    pub offset: usize,
    pub count: Option<usize>,
}

impl Default for RangeScoreSpec {
    #[inline]
    fn default() -> Self {
        Self {
            min: f64::NEG_INFINITY,
            max: f64::INFINITY,
            minex: false,
            maxex: false,
            offset: 0,
            count: None,
        }
    }
}

impl RangeScoreSpec {
    #[inline]
    pub fn new(min: f64, max: f64) -> Self {
        Self {
            min,
            max,
            ..Default::default()
        }
    }

    #[inline]
    pub fn with_limit(min: f64, max: f64, offset: usize, count: usize) -> Self {
        Self {
            min,
            max,
            minex: false,
            maxex: false,
            offset,
            count: Some(count),
        }
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.min > self.max || (self.min == self.max && (self.minex || self.maxex))
    }

    #[inline]
    pub fn check(&self, score: f64) -> bool {
        let min_ok = if self.minex {
            score > self.min
        } else {
            score >= self.min
        };
        let max_ok = if self.maxex {
            score < self.max
        } else {
            score <= self.max
        };
        min_ok && max_ok
    }

    /// 从 Redis 分数边界字符串构造 RangeScoreSpec
    pub fn from_bounds(
        min_bound: &str,
        max_bound: &str,
        offset: usize,
        count: Option<usize>,
    ) -> Result<Self> {
        let (min, minex) = Self::parse_bound(min_bound)?;
        let (max, maxex) = Self::parse_bound(max_bound)?;
        Ok(Self {
            min,
            max,
            minex,
            maxex,
            offset,
            count,
        })
    }

    /// 解析 Redis 风格的分数边界字符串(例如 "(1.5", "[10", "10", "-inf", "+inf", "(-inf" 等)
    pub fn parse_bound(s: &str) -> Result<(f64, bool)> {
        let s = s.trim();
        if s.is_empty() {
            return Err(Error::invalid_data("ERR min or max is not a float"));
        }

        let (val_str, is_exclusive) = if let Some(rest) = s.strip_prefix('(') {
            (rest.trim(), true)
        } else if let Some(rest) = s.strip_prefix('[') {
            (rest.trim(), false)
        } else {
            (s, false)
        };

        if val_str.eq_ignore_ascii_case("-inf") || val_str.eq_ignore_ascii_case("-infinity") {
            return Ok((f64::NEG_INFINITY, is_exclusive));
        }
        if val_str.eq_ignore_ascii_case("+inf")
            || val_str.eq_ignore_ascii_case("+infinity")
            || val_str.eq_ignore_ascii_case("inf")
            || val_str.eq_ignore_ascii_case("infinity")
        {
            return Ok((f64::INFINITY, is_exclusive));
        }

        let val = val_str
            .parse::<f64>()
            .map_err(|_| Error::invalid_data("ERR min or max is not a float"))?;
        if val.is_nan() {
            return Err(Error::invalid_data("ERR min or max is not a float"));
        }
        Ok((val, is_exclusive))
    }
}

/// 字典序范围规格(对标 Apache Kvrocks RangeLexSpec,统一用于 ZSet + Hash)
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RangeLexSpec {
    pub min: Vec<u8>,
    pub max: Vec<u8>,
    pub minex: bool,
    pub maxex: bool,
    pub min_infinite: bool,
    pub max_infinite: bool,
    pub offset: usize,
    pub count: Option<usize>,
    pub reversed: bool,
}

pub use crate::error::ERR_WRONG_TYPE;

impl RangeLexSpec {
    #[inline]
    pub fn new(min: impl Into<Vec<u8>>, max: impl Into<Vec<u8>>) -> Self {
        Self {
            min: min.into(),
            max: max.into(),
            minex: false,
            maxex: false,
            min_infinite: false,
            max_infinite: false,
            offset: 0,
            count: None,
            reversed: false,
        }
    }

    #[inline]
    pub fn unbounded() -> Self {
        Self {
            min_infinite: true,
            max_infinite: true,
            ..Default::default()
        }
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        !self.min_infinite
            && !self.max_infinite
            && (self.min > self.max || (self.min == self.max && (self.minex || self.maxex)))
    }

    #[inline]
    pub fn check(&self, member: &[u8]) -> bool {
        let min_ok = if self.min_infinite {
            true
        } else if self.minex {
            member > self.min.as_slice()
        } else {
            member >= self.min.as_slice()
        };

        let max_ok = if self.max_infinite {
            true
        } else if self.maxex {
            member < self.max.as_slice()
        } else {
            member <= self.max.as_slice()
        };

        min_ok && max_ok
    }

    /// 从 Redis 边界字符串或字节切片构造 RangeLexSpec(严格校验 min 为 -/(/[,max 为 +/(/[)
    pub fn from_bounds(
        min_bound: &[u8],
        max_bound: &[u8],
        offset: usize,
        count: Option<usize>,
    ) -> Result<Self> {
        let (min, minex, min_infinite) = Self::parse_min_bound(min_bound)?;
        let (max, maxex, max_infinite) = Self::parse_max_bound(max_bound)?;
        Ok(Self {
            min,
            max,
            minex,
            maxex,
            min_infinite,
            max_infinite,
            offset,
            count,
            reversed: false,
        })
    }

    /// 解析 Redis 字典序下界(合法的 min 为 "-" 或以 '(' / '[' 开头)
    pub fn parse_min_bound(bound: &[u8]) -> Result<(Vec<u8>, bool, bool)> {
        if bound == b"-" {
            return Ok((Vec::new(), false, true));
        }
        if bound == b"+" {
            return Err(Error::invalid_data(
                "ERR min or max not valid string range item",
            ));
        }
        if let Some(rest) = bound.strip_prefix(b"(") {
            Ok((rest.to_vec(), true, false))
        } else if let Some(rest) = bound.strip_prefix(b"[") {
            Ok((rest.to_vec(), false, false))
        } else {
            Err(Error::invalid_data(
                "ERR min or max not valid string range item",
            ))
        }
    }

    /// 解析 Redis 字典序上界(合法的 max 为 "+" 或以 '(' / '[' 开头)
    pub fn parse_max_bound(bound: &[u8]) -> Result<(Vec<u8>, bool, bool)> {
        if bound == b"+" {
            return Ok((Vec::new(), false, true));
        }
        if bound == b"-" {
            return Err(Error::invalid_data(
                "ERR min or max not valid string range item",
            ));
        }
        if let Some(rest) = bound.strip_prefix(b"(") {
            Ok((rest.to_vec(), true, false))
        } else if let Some(rest) = bound.strip_prefix(b"[") {
            Ok((rest.to_vec(), false, false))
        } else {
            Err(Error::invalid_data(
                "ERR min or max not valid string range item",
            ))
        }
    }

    /// 通用解析 Redis 字典序边界(支持 "-", "+", "(abc", "[abc")
    pub fn parse_bound(bound: &[u8]) -> Result<(Vec<u8>, bool, bool)> {
        if bound == b"-" {
            return Ok((Vec::new(), false, true));
        }
        if bound == b"+" {
            return Ok((Vec::new(), false, true));
        }
        if let Some(rest) = bound.strip_prefix(b"(") {
            Ok((rest.to_vec(), true, false))
        } else if let Some(rest) = bound.strip_prefix(b"[") {
            Ok((rest.to_vec(), false, false))
        } else {
            Err(Error::invalid_data(
                "ERR min or max not valid string range item",
            ))
        }
    }
}

/// 排名范围规格(对标 Apache Kvrocks RangeRankSpec)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RangeRankSpec {
    pub start: i64,
    pub stop: i64,
    pub reversed: bool,
}

impl RangeRankSpec {
    #[inline]
    pub fn new(start: i64, stop: i64) -> Self {
        Self {
            start,
            stop,
            reversed: false,
        }
    }

    #[inline]
    pub fn rev(start: i64, stop: i64) -> Self {
        Self {
            start,
            stop,
            reversed: true,
        }
    }
}

/// 统一 ZRANGE 选项配置(对标 Redis 6.2+ ZRANGE 全参选项)
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ZRangeSpec {
    pub by_score: bool,
    pub by_lex: bool,
    pub rev: bool,
    pub with_scores: bool,
    pub offset: usize,
    pub count: Option<usize>,
}

pub type ZRange = ZRangeSpec;