1#[cfg(not(feature = "std"))]
12use crate::nostd_prelude::*;
13use alloc::collections::BTreeMap;
14#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
15#[cfg(not(any(
16 feature = "external-clock",
17 all(target_arch = "wasm32", target_os = "unknown")
18)))]
19use std::time::{SystemTime, UNIX_EPOCH};
20
21use kevy_map::KevyMap;
22
23use crate::StoreError;
24use crate::value::{BTREE_SLOT_BYTES, SmallBytes};
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
32pub struct StreamId {
33 pub ms: u64,
35 pub seq: u64,
37}
38
39impl StreamId {
40 pub const MIN: StreamId = StreamId { ms: 0, seq: 0 };
42 pub const MAX: StreamId = StreamId { ms: u64::MAX, seq: u64::MAX };
44
45 pub fn encode(self) -> Vec<u8> {
47 format!("{}-{}", self.ms, self.seq).into_bytes()
48 }
49
50 #[must_use]
52 pub fn next(self) -> Self {
53 if self.seq < u64::MAX {
54 StreamId { ms: self.ms, seq: self.seq + 1 }
55 } else if self.ms < u64::MAX {
56 StreamId { ms: self.ms + 1, seq: 0 }
57 } else {
58 StreamId::MAX
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub enum XAddIdSpec {
67 AutoAll,
69 AutoSeq(u64),
71 Explicit(StreamId),
73}
74
75pub fn parse_xadd_id(s: &[u8]) -> Result<XAddIdSpec, StreamIdError> {
77 if s == b"*" {
78 return Ok(XAddIdSpec::AutoAll);
79 }
80 let txt = core::str::from_utf8(s).map_err(|_| StreamIdError::Invalid)?;
81 match txt.split_once('-') {
82 None => {
83 let ms = txt.parse::<u64>().map_err(|_| StreamIdError::Invalid)?;
84 Ok(XAddIdSpec::Explicit(StreamId { ms, seq: 0 }))
85 }
86 Some((ms_s, seq_s)) => {
87 let ms = ms_s.parse::<u64>().map_err(|_| StreamIdError::Invalid)?;
88 if seq_s == "*" {
89 Ok(XAddIdSpec::AutoSeq(ms))
90 } else {
91 let seq = seq_s.parse::<u64>().map_err(|_| StreamIdError::Invalid)?;
92 Ok(XAddIdSpec::Explicit(StreamId { ms, seq }))
93 }
94 }
95 }
96}
97
98pub fn parse_range_start(s: &[u8]) -> Result<StreamId, StreamIdError> {
101 if s == b"-" {
102 return Ok(StreamId::MIN);
103 }
104 parse_explicit_id(s, false)
105}
106
107pub fn parse_range_end(s: &[u8]) -> Result<StreamId, StreamIdError> {
110 if s == b"+" {
111 return Ok(StreamId::MAX);
112 }
113 parse_explicit_id(s, true)
114}
115
116pub fn parse_explicit_id(s: &[u8], end: bool) -> Result<StreamId, StreamIdError> {
120 let txt = core::str::from_utf8(s).map_err(|_| StreamIdError::Invalid)?;
121 let (ms_s, seq_s) = match txt.split_once('-') {
122 Some(p) => p,
123 None => (txt, if end { "" } else { "0" }),
124 };
125 let ms = ms_s.parse::<u64>().map_err(|_| StreamIdError::Invalid)?;
126 let seq = if seq_s.is_empty() {
127 u64::MAX
128 } else {
129 seq_s.parse::<u64>().map_err(|_| StreamIdError::Invalid)?
130 };
131 Ok(StreamId { ms, seq })
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum StreamIdError {
139 Invalid,
141}
142
143#[derive(Default, Clone)]
150pub struct StreamData {
151 pub(super) entries: BTreeMap<StreamId, Vec<(SmallBytes, SmallBytes)>>,
153 pub(super) last_id: StreamId,
156 pub(super) max_deleted_id: StreamId,
159 pub(super) entries_added: u64,
162 pub(super) groups: KevyMap<SmallBytes, Box<group::ConsumerGroup>>,
165}
166
167impl StreamData {
168 pub fn length(&self) -> u64 {
170 self.entries.len() as u64
171 }
172
173 pub fn last_id(&self) -> StreamId {
176 self.last_id
177 }
178
179 pub fn entries_added(&self) -> u64 {
181 self.entries_added
182 }
183
184 pub fn max_deleted_id(&self) -> StreamId {
188 self.max_deleted_id
189 }
190
191 pub fn iter_entries(&self) -> impl Iterator<Item = (StreamId, &[(SmallBytes, SmallBytes)])> {
194 self.entries.iter().map(|(id, fv)| (*id, fv.as_slice()))
195 }
196
197 pub fn first_entry(&self) -> Option<(StreamId, &[(SmallBytes, SmallBytes)])> {
199 self.entries.iter().next().map(|(id, fv)| (*id, fv.as_slice()))
200 }
201
202 pub fn last_entry(&self) -> Option<(StreamId, &[(SmallBytes, SmallBytes)])> {
204 self.entries.iter().next_back().map(|(id, fv)| (*id, fv.as_slice()))
205 }
206
207 pub fn groups_iter(&self) -> impl Iterator<Item = (&[u8], &group::ConsumerGroup)> {
209 self.groups.iter().map(|(k, v)| (k.as_slice(), v.as_ref()))
210 }
211
212 pub fn group(&self, name: &[u8]) -> Option<&group::ConsumerGroup> {
214 self.groups.get(name).map(core::convert::AsRef::as_ref)
215 }
216
217 pub fn group_count(&self) -> usize {
219 self.groups.len()
220 }
221
222 pub fn load_entry(&mut self, id: StreamId, fields: Vec<(SmallBytes, SmallBytes)>) {
226 self.entries.insert(id, fields);
227 }
228
229 pub fn set_loaded_state(
232 &mut self,
233 last_id: StreamId,
234 max_deleted_id: StreamId,
235 entries_added: u64,
236 ) {
237 self.last_id = last_id;
238 self.max_deleted_id = max_deleted_id;
239 self.entries_added = entries_added;
240 }
241
242 pub(crate) fn insert(&mut self, id: StreamId, fields: Vec<(SmallBytes, SmallBytes)>) {
245 debug_assert!(id > self.last_id || (id == StreamId::MIN && self.last_id == StreamId::MIN));
246 self.entries.insert(id, fields);
247 self.last_id = id;
248 self.entries_added += 1;
249 }
250
251 pub fn resolve_xadd_id(&self, spec: XAddIdSpec, now_ms: u64) -> Result<StreamId, StoreError> {
255 let candidate = match spec {
256 XAddIdSpec::AutoAll => {
257 let ms = now_ms.max(self.last_id.ms);
258 if ms == self.last_id.ms {
259 StreamId { ms, seq: self.last_id.seq + 1 }
260 } else {
261 StreamId { ms, seq: 0 }
262 }
263 }
264 XAddIdSpec::AutoSeq(ms) => {
265 if ms < self.last_id.ms {
266 return Err(StoreError::OutOfRange);
267 }
268 if ms == self.last_id.ms {
269 StreamId { ms, seq: self.last_id.seq + 1 }
270 } else {
271 StreamId { ms, seq: 0 }
272 }
273 }
274 XAddIdSpec::Explicit(id) => {
275 if id <= self.last_id {
276 return Err(StoreError::OutOfRange);
277 }
278 if id == StreamId::MIN {
279 return Err(StoreError::OutOfRange);
280 }
281 id
282 }
283 };
284 Ok(candidate)
285 }
286
287 pub fn range(
289 &self,
290 start: StreamId,
291 end: StreamId,
292 count: Option<usize>,
293 ) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
294 let iter = self.entries.range(start..=end).map(|(id, fv)| (*id, fv.as_slice()));
295 match count {
296 Some(n) => iter.take(n).collect(),
297 None => iter.collect(),
298 }
299 }
300
301 pub fn revrange(
303 &self,
304 start: StreamId,
305 end: StreamId,
306 count: Option<usize>,
307 ) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
308 let iter = self.entries.range(start..=end).rev().map(|(id, fv)| (*id, fv.as_slice()));
309 match count {
310 Some(n) => iter.take(n).collect(),
311 None => iter.collect(),
312 }
313 }
314
315 pub fn read_after(
317 &self,
318 last_seen: StreamId,
319 count: Option<usize>,
320 ) -> Vec<(StreamId, &[(SmallBytes, SmallBytes)])> {
321 if last_seen == StreamId::MAX {
322 return Vec::new();
323 }
324 self.range(last_seen.next(), StreamId::MAX, count)
325 }
326
327 pub(crate) fn del_ids(&mut self, ids: &[StreamId]) -> usize {
331 let mut removed = 0usize;
332 for id in ids {
333 if self.entries.remove(id).is_some() {
334 removed += 1;
335 if *id > self.max_deleted_id {
336 self.max_deleted_id = *id;
337 }
338 }
339 }
340 removed
341 }
342
343 pub(crate) fn trim_maxlen(&mut self, n: usize) -> usize {
345 let len = self.entries.len();
346 if len <= n {
347 return 0;
348 }
349 let drop = len - n;
350 let mut removed = 0;
351 let drop_ids: Vec<StreamId> = self.entries.keys().copied().take(drop).collect();
352 for id in drop_ids {
353 self.entries.remove(&id);
354 if id > self.max_deleted_id {
355 self.max_deleted_id = id;
356 }
357 removed += 1;
358 }
359 removed
360 }
361
362 pub fn weight(&self) -> u64 {
365 let entry_sum: u64 = self
366 .entries
367 .values()
368 .map(|fv| {
369 24 + fv
370 .iter()
371 .map(|(f, v)| 48 + f.heap_bytes() as u64 + v.heap_bytes() as u64)
372 .sum::<u64>()
373 })
374 .sum();
375 (self.entries.len() as u64).saturating_mul(BTREE_SLOT_BYTES) + entry_sum
376 }
377
378 pub(crate) fn trim_minid(&mut self, floor: StreamId) -> usize {
380 let drop_ids: Vec<StreamId> = self.entries.range(..floor).map(|(id, _)| *id).collect();
381 let removed = drop_ids.len();
382 for id in drop_ids {
383 self.entries.remove(&id);
384 if id > self.max_deleted_id {
385 self.max_deleted_id = id;
386 }
387 }
388 removed
389 }
390}
391
392mod claim;
393mod group;
394mod load;
395mod store;
396#[allow(unused_imports)]
397pub use claim::AutoclaimResult;
398#[allow(unused_imports)]
399pub use group::{
400 ConsumerGroup, ConsumerState, GroupCreateMode, PelEntry, PendingExtended, PendingExtendedRow,
401 PendingSummary, ReadGroupId, XClaimOpts,
402};
403pub use load::{LoadedGroup, LoadedPelEntry};
404pub use store::EntryBatch;
405
406pub type LoadedStreamEntry = (u64, u64, Vec<(Vec<u8>, Vec<u8>)>);
410
411#[cfg(not(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown"))))]
419pub fn now_unix_ms() -> u64 {
420 SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_millis() as u64)
421}
422
423#[cfg(any(feature = "external-clock", all(target_arch = "wasm32", target_os = "unknown")))]
429pub fn now_unix_ms() -> u64 {
430 crate::clock::wall_now_unix_ms()
431}
432
433pub(super) fn stream_entry_weight(fields: &[(SmallBytes, SmallBytes)]) -> u64 {
434 BTREE_SLOT_BYTES
436 + 24
437 + fields
438 .iter()
439 .map(|(f, v)| 48 + f.heap_bytes() as u64 + v.heap_bytes() as u64)
440 .sum::<u64>()
441}
442
443pub(super) fn clone_entries(src: Vec<(StreamId, &[(SmallBytes, SmallBytes)])>) -> EntryBatch {
444 src.into_iter()
445 .map(|(id, fv)| (id, fv.iter().map(|(f, v)| (f.to_vec(), v.to_vec())).collect()))
446 .collect()
447}