use std::{
cmp::{Ordering, Reverse},
collections::{BTreeSet, BinaryHeap},
io::{self, Read, Write},
};
use bitflags::bitflags;
use gxhash::{GxBuildHasher, HashMap};
use wbase::{glob::glob_match, time::now_ticks};
use wresp::{
ExpireOption,
cmd_strings::RESP_ERR_GENERIC_UNSUPPORTED_OPERATION as RESP_ERR_UNSUPPORTED_OPERATION,
};
use wval::GarnetObjectType;
use crate::{
object_store_utils::GarnetObjectPayload,
types::{
ObjectInput,
expiration_queue::{ExpirationQueue, ExpirationQueueEntry},
object_output::{ObjectOutput, ObjectOutputFlags},
},
};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive,
)]
#[repr(u8)]
pub enum SortedSetOperation {
Zadd = 0,
Zcard = 1,
Zpopmax = 2,
Zscore = 3,
Zrem = 4,
Zcount = 5,
Zincrby = 6,
Zrank = 7,
Zrange = 8,
Geoadd = 9,
Geohash = 10,
Geodist = 11,
Geopos = 12,
Geosearch = 13,
Zrevrank = 14,
Zremrangebylex = 15,
Zremrangebyrank = 16,
Zremrangebyscore = 17,
Zlexcount = 18,
Zpopmin = 19,
Zrandmember = 20,
Zdiff = 21,
Zscan = 22,
Zmscore = 23,
Zexpire = 24,
Zttl = 25,
Zpersist = 26,
Zcollect = 27,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SortedSetRangeOpts: u8 {
const NONE = 0;
const BY_SCORE = 1;
const BY_LEX = 1 << 1;
const REVERSE = 1 << 2;
const STORE = 1 << 3;
const WITH_SCORES = 1 << 4;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortedSetOrderOperation {
ByRank,
ByScore,
ByLex,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum SortedSetExpireResult {
KeyNotFound = -2,
ExpireConditionNotMet = 0,
ExpireUpdated = 1,
KeyAlreadyExpired = 2,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SortedSetEntry {
pub score: f64,
pub member: Vec<u8>,
}
impl Eq for SortedSetEntry {}
impl PartialOrd for SortedSetEntry {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SortedSetEntry {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self
.score
.total_cmp(&other.score)
.then_with(|| self.member.cmp(&other.member))
}
}
#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)]
pub struct SortedSetWire {
pub entries: Vec<(Vec<u8>, f64)>,
pub expirations: Option<Vec<(Vec<u8>, i64)>>,
}
#[derive(Debug, Clone, Default)]
pub struct SortedSetObject {
pub sorted_set: BTreeSet<SortedSetEntry>,
pub sorted_set_dict: HashMap<Vec<u8>, f64>,
pub expiration_times: Option<HashMap<Vec<u8>, i64>>,
pub expiration_queue: Option<ExpirationQueue>,
pub heap_memory_size: i64,
}
impl SortedSetObject {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn len(&self) -> usize {
self.sorted_set_dict.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.sorted_set_dict.is_empty()
}
pub fn deserialize_from_slice(slice: &[u8]) -> io::Result<Self> {
let wire: SortedSetWire =
bitcode::decode(slice).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let mut obj = Self::new();
obj.sorted_set_dict.reserve(wire.entries.len());
let now = now_ticks();
for (member, score) in wire.entries {
obj.update_size(&member, true);
obj.sorted_set.insert(SortedSetEntry {
score,
member: member.clone(),
});
obj.sorted_set_dict.insert(member, score);
}
if let Some(expirations) = wire.expirations {
for (member, expiration) in expirations {
if expiration < now {
if let Some(score) = obj.sorted_set_dict.remove(&member) {
obj.sorted_set.remove(&SortedSetEntry {
score,
member: member.clone(),
});
obj.update_size(&member, false);
}
} else if obj.sorted_set_dict.contains_key(&member) {
obj.initialize_expiration_structures();
if let Some(times) = obj.expiration_times.as_mut() {
times.insert(member.clone(), expiration);
}
if let Some(queue) = obj.expiration_queue.as_mut() {
queue.push(Reverse(ExpirationQueueEntry {
expiration,
key: member,
}));
}
obj.update_expiration_size(true, true);
}
}
obj.cleanup_expiration_structures_if_empty();
}
Ok(obj)
}
pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
Self::deserialize_from_slice(&buf)
}
pub fn serialize_to_vec(&self) -> Vec<u8> {
let now = now_ticks();
let has_expirations = self.expiration_times.is_some();
let entries: Vec<(Vec<u8>, f64)> = self
.sorted_set_dict
.iter()
.filter(|(m, _)| !has_expirations || !self.is_expired_at(m, now))
.map(|(m, s)| (m.clone(), *s))
.collect();
let expirations = self.expiration_times.as_ref().and_then(|times| {
let active: Vec<(Vec<u8>, i64)> = times
.iter()
.filter(|(m, exp)| **exp >= now && self.sorted_set_dict.contains_key(*m))
.map(|(m, exp)| (m.clone(), *exp))
.collect();
if active.is_empty() {
None
} else {
Some(active)
}
});
let wire = SortedSetWire {
entries,
expirations,
};
bitcode::encode(&wire)
}
pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.serialize_to_vec())
}
pub fn from_entries(entries: Vec<(Vec<u8>, f64)>) -> Self {
let mut obj = Self::new();
obj.sorted_set_dict.reserve(entries.len());
for (member, score) in entries {
if !obj.sorted_set_dict.contains_key(&member) {
obj.update_size(&member, true);
obj.sorted_set.insert(SortedSetEntry {
score,
member: member.clone(),
});
obj.sorted_set_dict.insert(member, score);
}
}
obj
}
pub fn add(&mut self, member: &[u8], score: f64) -> bool {
if let Some(old_score) = self.sorted_set_dict.get_mut(member) {
if *old_score != score {
let old = *old_score;
*old_score = score;
let m = member.to_vec();
self.sorted_set.remove(&SortedSetEntry {
score: old,
member: m.clone(),
});
self.sorted_set.insert(SortedSetEntry { score, member: m });
}
false
} else {
let m = member.to_vec();
self.sorted_set_dict.insert(m.clone(), score);
self.sorted_set.insert(SortedSetEntry { score, member: m });
self.update_size(member, true);
true
}
}
pub fn rem(&mut self, member: &[u8]) -> Option<f64> {
if let Some((m, old_score)) = self.sorted_set_dict.remove_entry(member) {
self.sorted_set.remove(&SortedSetEntry {
score: old_score,
member: m,
});
self.update_size(member, false);
self.try_remove_expiration(member);
Some(old_score)
} else {
None
}
}
pub fn incr_by(&mut self, member: &[u8], delta: f64) -> f64 {
let current = self.try_get_score(member).unwrap_or(0.0);
let new_score = current + delta;
self.add(member, new_score);
new_score
}
pub fn pop_min(&mut self) -> Option<(Vec<u8>, f64)> {
self.pop_min_or_max(false).map(|(s, m)| (m, s))
}
pub fn pop_max(&mut self) -> Option<(Vec<u8>, f64)> {
self.pop_min_or_max(true).map(|(s, m)| (m, s))
}
pub fn operate_basic(
&mut self,
op: SortedSetOperation,
member: &[u8],
score: f64,
) -> Option<Vec<u8>> {
match op {
SortedSetOperation::Zadd => {
self.add(member, score);
None
}
SortedSetOperation::Zrem => {
let old = self.rem(member)?;
Some(old.to_string().into_bytes())
}
SortedSetOperation::Zscore => {
let s = self.try_get_score(member)?;
Some(s.to_string().into_bytes())
}
_ => None,
}
}
pub fn equals(&self, other: &SortedSetObject) -> bool {
if self.sorted_set_dict.len() != other.sorted_set_dict.len() {
return false;
}
for (key, value) in self.sorted_set_dict.iter() {
if self.is_expired(key) && self.is_expired(key) {
continue;
}
if self.is_expired(key) || self.is_expired(key) {
return false;
}
match other.sorted_set_dict.get(key) {
Some(other_value) if other_value == value => {}
_ => return false,
}
}
true
}
pub fn operate(
&mut self,
input: &ObjectInput,
output: &mut ObjectOutput,
resp_protocol_version: u8,
) -> bool {
if input.header.data[0] != GarnetObjectType::SortedSet as u8 {
output.output_flags |= ObjectOutputFlags::WRONG_TYPE;
output.payload.clear();
return true;
}
let Some(op) = sorted_set_op_from_header(input) else {
output.write_error(RESP_ERR_UNSUPPORTED_OPERATION.as_bytes());
return true;
};
match op {
SortedSetOperation::Zadd => self.sorted_set_add(input, output, resp_protocol_version),
SortedSetOperation::Zrem => self.sorted_set_remove(input, output),
SortedSetOperation::Zcard => self.sorted_set_length(output),
SortedSetOperation::Zpopmax => {
self.sorted_set_pop_min_or_max_count(input, output, resp_protocol_version, op)
}
SortedSetOperation::Zscore => self.sorted_set_score(input, output, resp_protocol_version),
SortedSetOperation::Zmscore => self.sorted_set_scores(input, output, resp_protocol_version),
SortedSetOperation::Zcount => self.sorted_set_count(input, output),
SortedSetOperation::Zincrby => {
self.sorted_set_increment(input, output, resp_protocol_version)
}
SortedSetOperation::Zrank => self.sorted_set_rank(input, output, resp_protocol_version, true),
SortedSetOperation::Zexpire => self.sorted_set_expire(input, output),
SortedSetOperation::Zttl => self.sorted_set_time_to_live(input, output),
SortedSetOperation::Zpersist => self.sorted_set_persist(input, output),
SortedSetOperation::Zcollect => self.sorted_set_collect(output),
SortedSetOperation::Geoadd => self.geo_add(input, output),
SortedSetOperation::Geohash => self.geo_hash(input, output, resp_protocol_version),
SortedSetOperation::Geodist => self.geo_distance(input, output, resp_protocol_version),
SortedSetOperation::Geopos => self.geo_position(input, output, resp_protocol_version),
SortedSetOperation::Zrange => self.sorted_set_range(input, output, resp_protocol_version),
SortedSetOperation::Zrevrank => {
self.sorted_set_rank(input, output, resp_protocol_version, false)
}
SortedSetOperation::Zremrangebylex => {
self.sorted_set_remove_or_count_range_by_lex(input, output, op)
}
SortedSetOperation::Zremrangebyrank => self.sorted_set_remove_range_by_rank(input, output),
SortedSetOperation::Zremrangebyscore => self.sorted_set_remove_range_by_score(input, output),
SortedSetOperation::Zlexcount => {
self.sorted_set_remove_or_count_range_by_lex(input, output, op)
}
SortedSetOperation::Zpopmin => {
self.sorted_set_pop_min_or_max_count(input, output, resp_protocol_version, op)
}
SortedSetOperation::Zrandmember => {
self.sorted_set_random_member(input, output, resp_protocol_version)
}
SortedSetOperation::Zscan => self.scan_operate(input, output, resp_protocol_version),
SortedSetOperation::Geosearch | SortedSetOperation::Zdiff => {
output.write_error(RESP_ERR_UNSUPPORTED_OPERATION.as_bytes());
}
}
if self.sorted_set_dict.is_empty() {
output.output_flags |= ObjectOutputFlags::REMOVE_KEY;
}
true
}
pub fn scan(
&self,
start: i64,
count: i64,
pattern: &[u8],
_is_no_value: bool,
) -> (Vec<Option<Vec<u8>>>, i64) {
let mut items: Vec<Option<Vec<u8>>> = Vec::new();
let mut cursor = start;
if (self.sorted_set_dict.len() as i64) < start {
return (items, 0);
}
let mut index = 0_i64;
let mut expired_keys_count = 0_i64;
for (member, score) in self.sorted_set_dict.iter() {
if self.is_expired(member) {
expired_keys_count += 1;
continue;
}
if index < start {
index += 1;
continue;
}
if pattern.is_empty() || glob_match(pattern, member) {
items.push(Some(member.clone()));
items.push(if score.is_finite() {
Some(ObjectOutput::format_double(*score).into_bytes())
} else {
None
});
}
cursor += 1;
if items.len() as i64 == count * 2 {
break;
}
}
if cursor + expired_keys_count == self.sorted_set_dict.len() as i64 {
cursor = 0;
}
(items, cursor)
}
pub fn copy_diff(
sorted_set_object1: Option<&SortedSetObject>,
sorted_set_object2: Option<&SortedSetObject>,
) -> HashMap<Vec<u8>, f64> {
let mut result = HashMap::with_hasher(GxBuildHasher::default());
let Some(obj1) = sorted_set_object1 else {
return result;
};
for (key, value) in obj1.sorted_set_dict.iter() {
let expired1 = obj1.is_expired(key);
match sorted_set_object2 {
None => {
if !expired1 {
result.insert(key.clone(), *value);
}
}
Some(obj2) => {
if !expired1 && !obj2.is_expired(key) && !obj2.sorted_set_dict.contains_key(key) {
result.insert(key.clone(), *value);
}
}
}
}
result
}
pub fn in_place_diff(
dict1: &mut HashMap<Vec<u8>, f64>,
sorted_set_object2: Option<&SortedSetObject>,
) {
let Some(obj2) = sorted_set_object2 else {
return;
};
let doomed: Vec<Vec<u8>> = dict1
.iter()
.filter(|(k, _)| !obj2.is_expired(k) && obj2.sorted_set_dict.contains_key(*k))
.map(|(k, _)| k.clone())
.collect();
for k in doomed {
dict1.remove(&k);
}
}
#[inline]
pub fn try_get_score(&self, key: &[u8]) -> Option<f64> {
if self.is_expired(key) {
return None;
}
self.sorted_set_dict.get(key).copied()
}
pub fn count(&self) -> usize {
let Some(times) = self.expiration_times.as_ref() else {
return self.sorted_set_dict.len();
};
let expired_keys_count = times.keys().filter(|k| self.is_expired(k)).count();
self.sorted_set_dict.len() - expired_keys_count
}
#[inline]
pub fn is_expired_at(&self, key: &[u8], now: i64) -> bool {
self
.expiration_times
.as_ref()
.and_then(|t| t.get(key))
.is_some_and(|&expiration| expiration < now)
}
#[inline]
pub fn is_expired(&self, key: &[u8]) -> bool {
self.is_expired_at(key, now_ticks())
}
#[inline]
pub fn has_expirable_items(&self) -> bool {
self.expiration_times.is_some()
}
pub fn initialize_expiration_structures(&mut self) {
if self.expiration_times.is_none() {
self.expiration_times = Some(HashMap::with_hasher(GxBuildHasher::default()));
self.expiration_queue = Some(BinaryHeap::new());
self.heap_memory_size += 16; }
}
#[inline]
pub fn update_expiration_size(&mut self, add: bool, include_pq: bool) {
let mut memory_size = 16 + 16;
if include_pq {
memory_size += 16 + 16;
}
if add {
self.heap_memory_size += memory_size;
} else {
self.heap_memory_size -= memory_size;
}
}
pub fn cleanup_expiration_structures_if_empty(&mut self) {
let Some(times) = self.expiration_times.as_ref() else {
return;
};
if !times.is_empty() {
return;
}
if let Some(queue) = self.expiration_queue.as_ref() {
self.heap_memory_size -= (16 + 16) * queue.len() as i64;
}
self.heap_memory_size -= 16; self.expiration_times = None;
self.expiration_queue = None;
}
pub fn delete_expired_items(&mut self) {
if self.expiration_times.is_none() {
return;
}
let now = now_ticks();
while let Some(queue) = self.expiration_queue.as_mut() {
let Some(Reverse(head)) = queue.peek() else {
break;
};
if head.expiration >= now {
break;
}
let key = head.key.clone();
let expiration = head.expiration;
let in_times = self
.expiration_times
.as_ref()
.and_then(|t| t.get(&key))
.is_some_and(|&actual| actual == expiration);
if in_times {
self.expiration_times.as_mut().unwrap().remove(&key);
queue.pop();
self.update_expiration_size(false, true);
if let Some(value) = self.sorted_set_dict.get(&key).copied() {
self.sorted_set_dict.remove(&key);
self.sorted_set.remove(&SortedSetEntry {
score: value,
member: key.clone(),
});
self.update_size(&key, false);
}
} else {
queue.pop();
self.heap_memory_size -= 16 + 16;
}
}
self.cleanup_expiration_structures_if_empty();
}
pub fn set_expiration(
&mut self,
key: &[u8],
expiration: i64,
expire_option: ExpireOption,
) -> SortedSetExpireResult {
if !self.sorted_set_dict.contains_key(key) {
return SortedSetExpireResult::KeyNotFound;
}
if expiration <= now_ticks() {
if let Some(value) = self.sorted_set_dict.remove(key) {
self.sorted_set.remove(&SortedSetEntry {
score: value,
member: key.to_vec(),
});
self.update_size(key, false);
}
return SortedSetExpireResult::KeyAlreadyExpired;
}
self.initialize_expiration_structures();
let current_expiration = self
.expiration_times
.as_ref()
.and_then(|t| t.get(key))
.copied();
let denied = match current_expiration {
Some(current) => {
expire_option.contains(ExpireOption::NX)
|| (expire_option.contains(ExpireOption::GT) && expiration <= current)
|| (expire_option.contains(ExpireOption::LT) && expiration >= current)
}
None => expire_option.contains(ExpireOption::XX) || expire_option.contains(ExpireOption::GT),
};
if denied {
return SortedSetExpireResult::ExpireConditionNotMet;
}
let key_vec = key.to_vec();
if current_expiration.is_some() {
if let Some(slot) = self.expiration_times.as_mut().unwrap().get_mut(key) {
*slot = expiration;
}
self.heap_memory_size += 16 + 16;
} else {
self
.expiration_times
.as_mut()
.unwrap()
.insert(key_vec.clone(), expiration);
self.update_expiration_size(true, true);
}
self
.expiration_queue
.as_mut()
.unwrap()
.push(Reverse(ExpirationQueueEntry {
expiration,
key: key_vec,
}));
SortedSetExpireResult::ExpireUpdated
}
pub fn persist(&mut self, key: &[u8]) -> i32 {
if !self.sorted_set_dict.contains_key(key) {
return -2;
}
if self.try_remove_expiration(key) {
1
} else {
-1
}
}
#[inline]
pub fn try_remove_expiration(&mut self, key: &[u8]) -> bool {
if self.expiration_times.is_none() {
return false;
}
self.try_remove_expiration_worker(key)
}
fn try_remove_expiration_worker(&mut self, key: &[u8]) -> bool {
let Some(times) = self.expiration_times.as_mut() else {
return false;
};
if times.remove(key).is_none() {
return false;
}
self.update_expiration_size(false, false);
self.cleanup_expiration_structures_if_empty();
true
}
pub fn get_expiration(&self, key: &[u8]) -> i64 {
if !self.sorted_set_dict.contains_key(key) {
return -2;
}
if let Some(&expiration) = self.expiration_times.as_ref().and_then(|t| t.get(key)) {
return expiration;
}
-1
}
pub fn element_at(&self, index: usize) -> Option<(Vec<u8>, f64)> {
if self.has_expirable_items() {
return self
.sorted_set_dict
.iter()
.filter(|(k, _)| !self.is_expired(k))
.nth(index)
.map(|(k, v)| (k.clone(), *v));
}
self
.sorted_set_dict
.iter()
.nth(index)
.map(|(k, v)| (k.clone(), *v))
}
#[inline]
pub fn update_size(&mut self, item: &[u8], add: bool) {
let memory_size = (item.len().div_ceil(8) * 8 + 16 + 16) as i64;
if add {
self.heap_memory_size += memory_size;
} else {
self.heap_memory_size -= memory_size;
}
}
}
#[inline]
pub fn sorted_set_op_from_header(input: &ObjectInput) -> Option<SortedSetOperation> {
SortedSetOperation::try_from(input.header.sub_id()).ok()
}
impl GarnetObjectPayload for SortedSetObject {
const OBJECT_TAG: GarnetObjectType = GarnetObjectType::SortedSet;
#[inline]
fn from_blob(raw: &[u8]) -> Self {
Self::deserialize_from_slice(raw).unwrap_or_default()
}
#[inline]
fn to_blob(&self) -> Vec<u8> {
self.serialize_to_vec()
}
#[inline]
fn is_empty(&self) -> bool {
self.sorted_set_dict.is_empty()
}
}