#[cfg(not(feature = "std"))]
use crate::nostd_prelude::*;
use alloc::collections::BTreeMap;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(any(
feature = "external-clock",
all(target_arch = "wasm32", target_os = "unknown")
)))]
use std::time::{SystemTime, UNIX_EPOCH};
use kevy_map::KevyMap;
use crate::StoreError;
use crate::value::{BTREE_SLOT_BYTES, SmallBytes};
mod id;
pub use id::{
StreamId, StreamIdError, XAddIdSpec, parse_explicit_id, parse_range_end, parse_range_start,
parse_xadd_id,
};
#[derive(Debug, Default, Clone)]
pub struct StreamData {
pub(super) entries: BTreeMap<StreamId, Vec<(SmallBytes, SmallBytes)>>,
pub(super) last_id: StreamId,
pub(super) max_deleted_id: StreamId,
pub(super) entries_added: u64,
pub(super) groups: KevyMap<SmallBytes, Box<group::ConsumerGroup>>,
pub(super) nodes: nodes::Nodes,
}
impl StreamData {
pub fn length(&self) -> u64 {
self.entries.len() as u64
}
pub fn last_id(&self) -> StreamId {
self.last_id
}
pub fn entries_added(&self) -> u64 {
self.entries_added
}
pub fn max_deleted_id(&self) -> StreamId {
self.max_deleted_id
}
pub fn entries(&self) -> impl Iterator<Item = (StreamId, &[(SmallBytes, SmallBytes)])> {
self.entries.iter().map(|(id, fv)| (*id, fv.as_slice()))
}
pub fn first_entry(&self) -> Option<(StreamId, &[(SmallBytes, SmallBytes)])> {
self.entries.iter().next().map(|(id, fv)| (*id, fv.as_slice()))
}
pub fn last_entry(&self) -> Option<(StreamId, &[(SmallBytes, SmallBytes)])> {
self.entries.iter().next_back().map(|(id, fv)| (*id, fv.as_slice()))
}
pub fn groups(&self) -> impl Iterator<Item = (&[u8], &group::ConsumerGroup)> {
self.groups.iter().map(|(k, v)| (k.as_slice(), v.as_ref()))
}
pub fn group(&self, name: &[u8]) -> Option<&group::ConsumerGroup> {
self.groups.get(name).map(core::convert::AsRef::as_ref)
}
pub fn group_count(&self) -> usize {
self.groups.len()
}
pub fn load_entry(&mut self, id: StreamId, fields: Vec<(SmallBytes, SmallBytes)>) {
self.nodes.append(id, &fields);
self.entries.insert(id, fields);
}
pub fn set_loaded_state(
&mut self,
last_id: StreamId,
max_deleted_id: StreamId,
entries_added: u64,
) {
self.last_id = last_id;
self.max_deleted_id = max_deleted_id;
self.entries_added = entries_added;
}
pub(crate) fn insert(&mut self, id: StreamId, fields: Vec<(SmallBytes, SmallBytes)>) {
debug_assert!(id > self.last_id || (id == StreamId::MIN && self.last_id == StreamId::MIN));
self.nodes.append(id, &fields);
self.entries.insert(id, fields);
self.last_id = id;
self.entries_added += 1;
}
pub fn resolve_xadd_id(&self, spec: XAddIdSpec, now_ms: u64) -> Result<StreamId, StoreError> {
let last = self.last_id;
if last == StreamId::MAX {
return Err(StoreError::StreamExhausted);
}
let candidate = match spec {
XAddIdSpec::AutoAll if now_ms > last.ms => StreamId::new(now_ms, 0),
XAddIdSpec::AutoAll => last.next(),
XAddIdSpec::AutoSeq(ms) if ms > last.ms => StreamId::new(ms, 0),
XAddIdSpec::AutoSeq(ms) if ms < last.ms || last.seq == u64::MAX => {
return Err(StoreError::OutOfRange);
}
XAddIdSpec::AutoSeq(ms) => StreamId::new(ms, last.seq + 1),
XAddIdSpec::Explicit(id) if id <= last || id == StreamId::MIN => {
return Err(StoreError::OutOfRange);
}
XAddIdSpec::Explicit(id) => id,
};
Ok(candidate)
}
pub fn range(
&self,
start: StreamId,
end: StreamId,
count: Option<usize>,
) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
if start > end {
return Vec::new();
}
let iter = self.entries.range(start..=end).map(|(id, fv)| (*id, fv.as_slice()));
match count {
Some(n) => iter.take(n).collect(),
None => iter.collect(),
}
}
pub fn revrange(
&self,
start: StreamId,
end: StreamId,
count: Option<usize>,
) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
if start > end {
return Vec::new();
}
let iter = self.entries.range(start..=end).rev().map(|(id, fv)| (*id, fv.as_slice()));
match count {
Some(n) => iter.take(n).collect(),
None => iter.collect(),
}
}
pub fn read_after(
&self,
last_seen: StreamId,
count: Option<usize>,
) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
if last_seen == StreamId::MAX {
return Vec::new();
}
self.range(last_seen.next(), StreamId::MAX, count)
}
pub(crate) fn del_ids(&mut self, ids: &[StreamId]) -> usize {
let mut removed = 0usize;
for id in ids {
if self.entries.remove(id).is_some() {
self.nodes.delete(*id);
removed += 1;
if *id > self.max_deleted_id {
self.max_deleted_id = *id;
}
}
}
removed
}
pub fn weight(&self) -> u64 {
let entry_sum: u64 = self
.entries
.values()
.map(|fv| {
24 + fv
.iter()
.map(|(f, v)| 48 + f.heap_bytes() as u64 + v.heap_bytes() as u64)
.sum::<u64>()
})
.sum();
(self.entries.len() as u64).saturating_mul(BTREE_SLOT_BYTES) + entry_sum
}
}
mod claim;
mod group;
mod lag;
mod load;
mod modes;
mod nodes;
mod pending;
mod restore;
mod store;
pub use claim::{AutoclaimResult, XClaimOpts};
pub use group::{ConsumerGroup, ConsumerState, GroupCreateMode, PelEntry, ReadGroupId};
pub use load::{LoadedGroup, LoadedPelEntry};
pub use modes::{AckMode, ClaimMode, MissingStream};
pub use nodes::{APPROX_TRIM_LIMIT, TrimMode, TrimTo};
pub use pending::{PendingExtended, PendingExtendedRow, PendingSummary};
pub use store::{EntryBatch, GroupBatch};
pub type LoadedStreamEntry = (u64, u64, Vec<(Vec<u8>, Vec<u8>)>);
#[cfg(not(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown"))))]
pub fn now_unix_ms() -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_millis() as u64)
}
#[cfg(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown")))]
pub fn now_unix_ms() -> u64 {
crate::clock::wall_now_unix_ms()
}
pub(super) fn stream_entry_weight(fields: &[(SmallBytes, SmallBytes)]) -> u64 {
BTREE_SLOT_BYTES
+ 24
+ fields
.iter()
.map(|(f, v)| 48 + f.heap_bytes() as u64 + v.heap_bytes() as u64)
.sum::<u64>()
}
pub(super) fn clone_entries(src: Vec<(StreamId, &[(SmallBytes, SmallBytes)])>) -> EntryBatch {
src.into_iter()
.map(|(id, fv)| (id, fv.iter().map(|(f, v)| (f.to_vec(), v.to_vec())).collect()))
.collect()
}