use bitflags::bitflags;
#[inline]
pub fn equals_ignore_case(a: &[u8], b: &[u8]) -> bool {
a.eq_ignore_ascii_case(b)
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct SortedSetAddOption: u8 {
const NONE = 0;
const None = 0;
const XX = 1;
const Xx = 1;
const NX = 1 << 1;
const Nx = 1 << 1;
const LT = 1 << 2;
const Lt = 1 << 2;
const GT = 1 << 3;
const Gt = 1 << 3;
const CH = 1 << 4;
const Ch = 1 << 4;
const INCR = 1 << 5;
const Incr = 1 << 5;
}
}
#[inline]
pub fn try_get_sorted_set_add_option(v: &[u8]) -> Option<SortedSetAddOption> {
match v.len() {
2 => {
let b0 = v[0].to_ascii_uppercase();
let b1 = v[1].to_ascii_uppercase();
match (b0, b1) {
(b'X', b'X') => Some(SortedSetAddOption::XX),
(b'N', b'X') => Some(SortedSetAddOption::NX),
(b'L', b'T') => Some(SortedSetAddOption::LT),
(b'G', b'T') => Some(SortedSetAddOption::GT),
(b'C', b'H') => Some(SortedSetAddOption::CH),
_ => None,
}
}
4 => {
let b0 = v[0].to_ascii_uppercase();
let b1 = v[1].to_ascii_uppercase();
let b2 = v[2].to_ascii_uppercase();
let b3 = v[3].to_ascii_uppercase();
if (b0, b1, b2, b3) == (b'I', b'N', b'C', b'R') {
Some(SortedSetAddOption::INCR)
} else {
None
}
}
_ => None,
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ExpireOption: u8 {
const NONE = 0;
const None = 0;
const NX = 1 << 0;
const Nx = 1 << 0;
const XX = 1 << 1;
const Xx = 1 << 1;
const GT = 1 << 2;
const Gt = 1 << 2;
const LT = 1 << 3;
const Lt = 1 << 3;
const XXGT = Self::XX.bits() | Self::GT.bits();
const XXLT = Self::XX.bits() | Self::LT.bits();
}
}
#[inline]
pub fn try_get_expire_option(v: &[u8]) -> Option<ExpireOption> {
if v.len() != 2 {
return None;
}
let b0 = v[0].to_ascii_uppercase();
let b1 = v[1].to_ascii_uppercase();
match (b0, b1) {
(b'N', b'X') => Some(ExpireOption::NX),
(b'X', b'X') => Some(ExpireOption::XX),
(b'G', b'T') => Some(ExpireOption::GT),
(b'L', b'T') => Some(ExpireOption::LT),
_ => None,
}
}
#[inline]
pub fn expire_option_from_token(arg: &[u8]) -> Option<ExpireOption> {
try_get_expire_option(arg)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ExpirationWithOption {
word: i64,
}
impl ExpirationWithOption {
#[inline]
pub fn new(expiration_time_in_ticks: i64, expire_option: ExpireOption) -> Self {
Self {
word: ((expiration_time_in_ticks >> 4) << 4) | (expire_option.bits() as i64 & 0xF),
}
}
#[inline]
pub fn from_word_head_tail(word_head: i32, word_tail: i32) -> Self {
Self {
word: ((((word_head as u32) as u64) << 32) | (word_tail as u32 as u64)) as i64,
}
}
#[inline]
pub fn expiration_time_in_ticks(&self) -> i64 {
(self.word >> 4) << 4
}
#[inline]
pub fn expire_option(&self) -> ExpireOption {
ExpireOption::from_bits_truncate((self.word & 0xF) as u8)
}
#[inline]
pub fn word(&self) -> i64 {
self.word
}
#[inline]
pub fn word_head(&self) -> i32 {
((self.word >> 32) & 0xFFFF_FFFF) as i32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ExpirationOption {
#[default]
None,
Ex,
Px,
Exat,
Pxat,
Keepttl,
}
#[inline]
pub fn try_get_expiration_option(token: &[u8]) -> Option<ExpirationOption> {
match token.len() {
2 => {
let b0 = token[0].to_ascii_uppercase();
let b1 = token[1].to_ascii_uppercase();
match (b0, b1) {
(b'E', b'X') => Some(ExpirationOption::Ex),
(b'P', b'X') => Some(ExpirationOption::Px),
_ => None,
}
}
4 => {
let b0 = token[0].to_ascii_uppercase();
let b1 = token[1].to_ascii_uppercase();
let b2 = token[2].to_ascii_uppercase();
let b3 = token[3].to_ascii_uppercase();
match (b0, b1, b2, b3) {
(b'E', b'X', b'A', b'T') => Some(ExpirationOption::Exat),
(b'P', b'X', b'A', b'T') => Some(ExpirationOption::Pxat),
_ => None,
}
}
7 => {
if token.eq_ignore_ascii_case(b"KEEPTTL") {
Some(ExpirationOption::Keepttl)
} else {
None
}
}
_ => None,
}
}
#[inline]
pub fn expiration_option_from_token(token: &[u8]) -> Option<ExpirationOption> {
try_get_expiration_option(token)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ExistOptions {
#[default]
None,
Nx,
Xx,
}
#[inline]
pub fn try_get_exist_options(token: &[u8]) -> Option<ExistOptions> {
if token.len() != 2 {
return None;
}
let b0 = token[0].to_ascii_uppercase();
let b1 = token[1].to_ascii_uppercase();
match (b0, b1) {
(b'N', b'X') => Some(ExistOptions::Nx),
(b'X', b'X') => Some(ExistOptions::Xx),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SortedSetAggregateType {
#[default]
Sum,
Min,
Max,
}
impl SortedSetAggregateType {
#[inline(always)]
pub fn apply(self, a: f64, b: f64) -> f64 {
match self {
Self::Sum => a + b,
Self::Min => a.min(b),
Self::Max => a.max(b),
}
}
}
#[inline]
pub fn try_get_sorted_set_aggregate_type(token: &[u8]) -> Option<SortedSetAggregateType> {
if token.len() != 3 {
return None;
}
let b0 = token[0].to_ascii_uppercase();
let b1 = token[1].to_ascii_uppercase();
let b2 = token[2].to_ascii_uppercase();
match (b0, b1, b2) {
(b'S', b'U', b'M') => Some(SortedSetAggregateType::Sum),
(b'M', b'I', b'N') => Some(SortedSetAggregateType::Min),
(b'M', b'A', b'X') => Some(SortedSetAggregateType::Max),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_equals_ignore_case() {
assert!(equals_ignore_case(b"hello", b"HELLO"));
assert!(equals_ignore_case(b"ZADD", b"zadd"));
assert!(!equals_ignore_case(b"ZADD", b"zadd1"));
assert!(!equals_ignore_case(b"", b"a"));
}
#[test]
fn test_sorted_set_add_options_parse() {
for (raw, expected) in [
(b"xx" as &[u8], SortedSetAddOption::XX),
(b"XX", SortedSetAddOption::XX),
(b"nx", SortedSetAddOption::NX),
(b"NX", SortedSetAddOption::NX),
(b"lt", SortedSetAddOption::LT),
(b"LT", SortedSetAddOption::LT),
(b"gt", SortedSetAddOption::GT),
(b"GT", SortedSetAddOption::GT),
(b"ch", SortedSetAddOption::CH),
(b"CH", SortedSetAddOption::CH),
(b"incr", SortedSetAddOption::INCR),
(b"INCR", SortedSetAddOption::INCR),
] {
assert_eq!(try_get_sorted_set_add_option(raw), Some(expected));
}
for bad in [b"" as &[u8], b"x", b"xxx", b"inc", b"incr1", b"zz"] {
assert_eq!(try_get_sorted_set_add_option(bad), None);
}
}
#[test]
fn test_expire_options_parse() {
for (raw, expected) in [
(b"nx" as &[u8], ExpireOption::NX),
(b"NX", ExpireOption::NX),
(b"xx", ExpireOption::XX),
(b"XX", ExpireOption::XX),
(b"gt", ExpireOption::GT),
(b"GT", ExpireOption::GT),
(b"lt", ExpireOption::LT),
(b"LT", ExpireOption::LT),
] {
assert_eq!(try_get_expire_option(raw), Some(expected));
assert_eq!(expire_option_from_token(raw), Some(expected));
}
for bad in [b"" as &[u8], b"n", b"nxx", b"zz", b"none"] {
assert_eq!(try_get_expire_option(bad), None);
assert_eq!(expire_option_from_token(bad), None);
}
}
#[test]
fn test_expiration_options_parse() {
for (raw, expected) in [
(b"ex" as &[u8], ExpirationOption::Ex),
(b"EX", ExpirationOption::Ex),
(b"px", ExpirationOption::Px),
(b"PX", ExpirationOption::Px),
(b"exat", ExpirationOption::Exat),
(b"EXAT", ExpirationOption::Exat),
(b"pxat", ExpirationOption::Pxat),
(b"PXAT", ExpirationOption::Pxat),
(b"keepttl", ExpirationOption::Keepttl),
(b"KEEPTTL", ExpirationOption::Keepttl),
] {
assert_eq!(try_get_expiration_option(raw), Some(expected));
assert_eq!(expiration_option_from_token(raw), Some(expected));
}
for bad in [b"" as &[u8], b"e", b"exx", b"pxa", b"keeptt", b"keepttll"] {
assert_eq!(try_get_expiration_option(bad), None);
assert_eq!(expiration_option_from_token(bad), None);
}
}
#[test]
fn test_exist_options_parse() {
assert_eq!(try_get_exist_options(b"nx"), Some(ExistOptions::Nx));
assert_eq!(try_get_exist_options(b"NX"), Some(ExistOptions::Nx));
assert_eq!(try_get_exist_options(b"xx"), Some(ExistOptions::Xx));
assert_eq!(try_get_exist_options(b"XX"), Some(ExistOptions::Xx));
assert_eq!(try_get_exist_options(b"none"), None);
assert_eq!(try_get_exist_options(b""), None);
}
#[test]
fn test_sorted_set_aggregate_type_parse() {
assert_eq!(
try_get_sorted_set_aggregate_type(b"sum"),
Some(SortedSetAggregateType::Sum)
);
assert_eq!(
try_get_sorted_set_aggregate_type(b"SUM"),
Some(SortedSetAggregateType::Sum)
);
assert_eq!(
try_get_sorted_set_aggregate_type(b"min"),
Some(SortedSetAggregateType::Min)
);
assert_eq!(
try_get_sorted_set_aggregate_type(b"MAX"),
Some(SortedSetAggregateType::Max)
);
assert_eq!(try_get_sorted_set_aggregate_type(b"avg"), None);
assert_eq!(try_get_sorted_set_aggregate_type(b"summ"), None);
assert_eq!(try_get_sorted_set_aggregate_type(b""), None);
}
#[test]
fn test_sorted_set_aggregate_apply() {
assert_eq!(SortedSetAggregateType::Sum.apply(2.5, 3.5), 6.0);
assert_eq!(SortedSetAggregateType::Min.apply(2.5, 3.5), 2.5);
assert_eq!(SortedSetAggregateType::Max.apply(2.5, 3.5), 3.5);
}
#[test]
fn test_expiration_with_option() {
let ticks = 1_000_000_000i64;
let opt = ExpireOption::GT;
let e = ExpirationWithOption::new(ticks, opt);
assert_eq!(e.expire_option(), ExpireOption::GT);
assert_eq!(e.expiration_time_in_ticks(), (ticks >> 4) << 4);
let reconstructed =
ExpirationWithOption::from_word_head_tail(e.word_head(), (e.word() & 0xFFFF_FFFF) as i32);
assert_eq!(e, reconstructed);
assert_eq!(e.word(), reconstructed.word());
}
}