wedb_embed 0.1.2

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

use crate::error::{Error, Result};

/// 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;