#![forbid(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(all(not(feature = "std"), not(feature = "external-clock")))]
compile_error!(
"kevy-store without `std` needs the `external-clock` feature: the TTL \
clock must be host-fed when std::time is unavailable"
);
extern crate alloc;
#[cfg(not(feature = "std"))]
pub(crate) mod nostd_prelude {
pub(crate) use alloc::boxed::Box;
pub(crate) use alloc::format;
pub(crate) use alloc::string::{String, ToString};
pub(crate) use alloc::vec::Vec;
}
#[cfg(not(feature = "std"))]
use nostd_prelude::*;
#[cfg(feature = "std")]
pub(crate) type SideMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "std"))]
pub(crate) type SideMap<K, V> = kevy_map::KevyMap<K, V>;
mod accounting;
#[cfg(feature = "std")]
mod bio_drop;
#[cfg(not(feature = "std"))]
impl Store {
#[inline]
pub(crate) fn maybe_offload_drop(&mut self, old: Value) {
drop(old);
}
}
mod bitmap;
mod clock;
mod entry;
mod error;
pub use error::{KevyError, KevyResult};
pub mod evict;
pub mod expire;
pub use expire::ExpireStats;
pub(crate) use entry::Entry;
mod hash;
mod hash_ttl;
pub use hash_ttl::{HExpireCode, HExpireCond};
mod keyspace;
mod keyspace_load;
mod list;
mod notify;
mod rng;
mod scan;
pub use notify::KeyspaceEvent;
mod list_ops;
mod set;
mod small_set;
pub use small_set::{SmallSetData, SmallSetIter};
mod small_hash;
pub use small_hash::{SmallHashData, SmallHashIter};
mod small_list;
pub use small_list::{SmallListData, SmallListIter};
mod small_zset;
pub use small_zset::{SmallZSetData, SmallZSetIter};
mod snapshot;
pub use snapshot::SnapshotView;
mod stream;
mod string;
mod string_rmw;
mod string_set;
mod tier;
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
mod tier_codec;
mod tier_demote;
mod tier_serve;
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub use tier::TierStats;
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub use tier_serve::{ColdBatchReader, ColdRead, PeekRow, SyncColdRead};
mod types;
pub use types::{EvictionPolicy, RenameOutcome, StoreError};
mod util;
mod value;
mod zset;
mod zset_algebra;
mod zset_range;
pub use zset_algebra::{ZAggregate, zdiff, zinter, zintercard, zunion};
mod zset_flags;
pub use zset_flags::{ZaddFlags, ZaddReport};
pub use stream::{
AutoclaimResult, ConsumerGroup, ConsumerState, EntryBatch, GroupCreateMode,
LoadedGroup, LoadedPelEntry, LoadedStreamEntry, PelEntry, PendingExtended,
PendingExtendedRow, PendingSummary, ReadGroupId, StreamData, StreamId, StreamIdError,
XAddIdSpec, XClaimOpts, now_unix_ms, parse_explicit_id, parse_range_end,
parse_range_start, parse_xadd_id,
};
pub use string::{GetReply, GetShared};
pub use util::glob_match;
pub use value::*;
pub(crate) use clock::{deadline_at, now_ns, pack_deadline, remaining_ms};
use kevy_map::KevyMap;
#[cfg(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown")))]
pub use clock::set_clock_ns;
#[cfg(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown")))]
pub use clock::set_wall_clock_ms;
#[derive(Default)]
pub struct Store {
pub(crate) map: KevyMap<SmallBytes, Entry>,
pub(crate) rng: rng::Rng,
pub(crate) hfttl: SideMap<SmallBytes, KevyMap<SmallBytes, u64>>,
pub(crate) cached_ns: u64,
pub(crate) cached_clock: bool,
pub(crate) used_memory: u64,
pub(crate) maxmemory: u64,
pub(crate) eviction_policy: EvictionPolicy,
pub(crate) evictions_total: u64,
pub(crate) clock_counter: u64,
pub(crate) used_memory_peak: u64,
pub(crate) expired_keys_total: u64,
pub(crate) notify_capture: u8,
pub(crate) notify_events: Vec<(notify::KeyspaceEvent, Vec<u8>)>,
pub(crate) expires: u64,
pub(crate) watch_versions: SideMap<Vec<u8>, u64>,
#[cfg(feature = "std")]
pub(crate) bio_drop_sender: Option<value::BioDropSender>,
#[cfg(feature = "std")]
pub(crate) pending_drops: Vec<Value>,
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub(crate) tier: Option<tier::TierState>,
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub(crate) tier_scratch: Option<Entry>,
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub(crate) tier_peek: bool,
}
impl Store {
pub fn new() -> Self {
Store::default()
}
#[inline]
pub fn refresh_clock(&mut self) {
self.cached_ns = now_ns();
}
#[inline]
pub fn set_cached_clock(&mut self, on: bool) {
self.cached_clock = on;
if on {
self.refresh_clock();
}
}
#[inline]
pub fn set_max_memory(&mut self, maxmemory: u64, policy: EvictionPolicy) {
self.maxmemory = maxmemory;
self.eviction_policy = policy;
}
#[inline]
pub fn used_memory(&self) -> u64 {
self.used_memory
}
#[inline]
pub fn used_memory_peak(&self) -> u64 {
self.used_memory_peak
}
#[inline]
pub fn maxmemory(&self) -> u64 {
self.maxmemory
}
#[inline]
pub fn eviction_policy(&self) -> EvictionPolicy {
self.eviction_policy
}
#[inline]
pub fn evictions_total(&self) -> u64 {
self.evictions_total
}
#[inline]
pub fn expires_count(&self) -> usize {
self.expires as usize
}
#[inline]
pub(crate) fn adjust_expires(&mut self, delta: i64) {
if delta != 0 {
self.expires = (self.expires as i64 + delta).max(0) as u64;
}
}
pub fn record_watch(&mut self, key: &[u8]) -> u64 {
#[cfg(feature = "std")]
{
*self
.watch_versions
.entry(key.to_vec())
.or_insert(0)
}
#[cfg(not(feature = "std"))]
{
if self.watch_versions.get(key).is_none() {
self.watch_versions.insert(key.to_vec(), 0);
}
self.watch_versions.get(key).copied().unwrap_or(0)
}
}
#[inline]
pub fn key_version(&self, key: &[u8]) -> u64 {
self.watch_versions.get(key).copied().unwrap_or(0)
}
#[inline]
pub fn bump_if_watched(&mut self, key: &[u8]) {
if self.watch_versions.is_empty() {
return;
}
if let Some(v) = self.watch_versions.get_mut(key) {
*v = v.wrapping_add(1);
}
}
pub fn bump_all_watched(&mut self) {
#[cfg(feature = "std")]
for v in self.watch_versions.values_mut() {
*v = v.wrapping_add(1);
}
#[cfg(not(feature = "std"))]
for (_, v) in self.watch_versions.iter_mut() {
*v = v.wrapping_add(1);
}
}
pub fn estimate_key_bytes(&self, key: &[u8]) -> Option<u64> {
self.map.get(key).map(|e| e.weight() + ENTRY_OVERHEAD)
}
#[inline]
pub fn precheck_for_write(&self) -> Result<(), StoreError> {
if self.maxmemory == 0 || self.used_memory <= self.maxmemory {
return Ok(());
}
if self.eviction_policy == EvictionPolicy::NoEviction {
return Err(StoreError::OutOfMemory);
}
Ok(())
}
#[inline]
pub fn try_evict_after_write(&mut self) -> usize {
if self.maxmemory == 0 || self.used_memory <= self.maxmemory {
return 0;
}
evict::evict_until_under_limit(self)
}
}
pub(crate) use util::{apply_delta, key_heap_bytes_for};
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_memory;
#[cfg(test)]
mod tests_snapshot;
#[cfg(test)]
mod tests_string_encoding;
#[cfg(all(test, feature = "std", not(target_arch = "wasm32")))]
mod tests_tier;
#[cfg(all(test, feature = "std", not(target_arch = "wasm32")))]
mod tests_tier_peek;