use std::time::Duration;
use crate::api::error::{ApiError, CasMismatch, HistoryCompacted, SequenceConflict};
use crate::api::types::{Event, ScanResult, StreamMeta};
use crate::branch::{Timestamp, current_timestamp};
use crate::db::{Database, DatabaseError};
const EVENT_SEPARATOR: u8 = 0x00;
const SEQ_WIDTH: usize = 8;
const TS_WIDTH: usize = 8;
#[derive(Debug)]
pub struct EventStore {
db: Database,
}
impl EventStore {
#[must_use]
pub const fn new(db: Database) -> Self {
Self { db }
}
#[must_use]
pub const fn database(&self) -> &Database {
&self.db
}
#[must_use]
pub fn into_database(self) -> Database {
self.db
}
pub fn append(
&self,
stream_key: &[u8],
payload: &[u8],
expected_seq: u64,
) -> Result<u64, ApiError> {
self.append_batch_with_ttl(stream_key, &[payload], expected_seq, None)
}
pub fn append_with_ttl(
&self,
stream_key: &[u8],
payload: &[u8],
expected_seq: u64,
ttl: Option<Duration>,
) -> Result<u64, ApiError> {
self.append_batch_with_ttl(stream_key, &[payload], expected_seq, ttl)
}
pub fn append_batch(
&self,
stream_key: &[u8],
payloads: &[&[u8]],
expected_seq: u64,
) -> Result<u64, ApiError> {
self.append_batch_with_ttl(stream_key, payloads, expected_seq, None)
}
pub fn append_batch_with_ttl(
&self,
stream_key: &[u8],
payloads: &[&[u8]],
expected_seq: u64,
ttl: Option<Duration>,
) -> Result<u64, ApiError> {
let timestamp = current_timestamp();
let entries: Vec<Vec<u8>> = payloads
.iter()
.map(|payload| encode_value(timestamp, payload))
.collect();
match self
.db
.append_with_ttl(stream_key.to_vec(), entries, expected_seq, ttl)
{
Ok(next_seq) => Ok(next_seq),
Err(DatabaseError::SequenceConflict { expected, actual }) => {
Err(ApiError::SequenceConflict(SequenceConflict {
expected,
actual,
}))
}
Err(error) => Err(ApiError::from(error)),
}
}
pub fn read(&self, stream_key: &[u8]) -> Result<Vec<Event>, ApiError> {
self.read_from(stream_key, 0)
}
pub fn read_from(&self, stream_key: &[u8], from_seq: u64) -> Result<Vec<Event>, ApiError> {
let engine_from = from_seq.saturating_add(1);
let entries = self.db.read_event_entries_from(stream_key, engine_from)?;
let next_seq = self.db.read_stream_next_seq(stream_key)?;
let mut events = Vec::with_capacity(entries.len());
for (key, value) in entries {
let seq = decode_event_seq(stream_key, &key)?;
let (timestamp, payload) = decode_value(&value)?;
events.push(Event::new(seq, payload, timestamp));
}
if events.is_empty() && from_seq == 0 && next_seq.is_some_and(|next_seq| next_seq > 0) {
return Err(ApiError::HistoryCompacted(HistoryCompacted::new(
stream_key.to_vec(),
)));
}
Ok(events)
}
pub fn read_value(&self, key: &[u8]) -> Result<Option<u64>, ApiError> {
Ok(self.db.read_value(key)?)
}
pub fn cas(&self, key: &[u8], expected: Option<u64>, new: u64) -> Result<(), ApiError> {
match self.db.cas(key.to_vec(), expected, new) {
Ok(()) => Ok(()),
Err(DatabaseError::CasMismatch { expected, actual }) => {
Err(ApiError::CasMismatch(CasMismatch { expected, actual }))
}
Err(error) => Err(ApiError::from(error)),
}
}
pub fn scan<P>(&self, mut predicate: P) -> Result<Vec<ScanResult>, ApiError>
where
P: FnMut(StreamMeta<'_>) -> bool,
{
let streams = self.db.scan_sequence_keys()?;
let mut matches = Vec::new();
for (stream_key, next_seq) in &streams {
let meta = StreamMeta {
stream_key,
next_seq: *next_seq,
};
if predicate(meta) && self.db.stream_has_live_events(stream_key)? {
matches.push(ScanResult::new(stream_key.clone(), *next_seq));
}
}
Ok(matches)
}
pub fn flush(&self) -> Result<(), ApiError> {
self.db.commit()?;
Ok(())
}
}
#[must_use]
pub fn encode_stream_key(stream_key: &[u8], seq: u64) -> Vec<u8> {
let mut encoded = Vec::with_capacity(stream_key.len().saturating_add(1 + SEQ_WIDTH));
encoded.extend_from_slice(stream_key);
encoded.push(EVENT_SEPARATOR);
encoded.extend_from_slice(&seq.to_be_bytes());
encoded
}
#[must_use]
pub fn decode_stream_key(encoded: &[u8]) -> Option<(Vec<u8>, u64)> {
let split = encoded.len().checked_sub(SEQ_WIDTH)?;
let separator_index = split.checked_sub(1)?;
if encoded.get(separator_index) != Some(&EVENT_SEPARATOR) {
return None;
}
let stream_key = encoded.get(..separator_index)?.to_vec();
let seq_bytes: [u8; SEQ_WIDTH] = encoded.get(split..)?.try_into().ok()?;
Some((stream_key, u64::from_be_bytes(seq_bytes)))
}
fn decode_event_seq(stream_key: &[u8], encoded: &[u8]) -> Result<u64, ApiError> {
match decode_stream_key(encoded) {
Some((decoded_key, engine_seq)) if decoded_key == stream_key => engine_seq
.checked_sub(1)
.ok_or_else(|| ApiError::CorruptEvent(format!("event key has zero seq: {encoded:?}"))),
_ => Err(ApiError::CorruptEvent(format!(
"event key does not encode stream {stream_key:?}: {encoded:?}"
))),
}
}
fn encode_value(timestamp: Timestamp, payload: &[u8]) -> Vec<u8> {
encode_event_value(timestamp, payload)
}
pub(crate) fn encode_event_value(timestamp: Timestamp, payload: &[u8]) -> Vec<u8> {
let mut encoded = Vec::with_capacity(TS_WIDTH.saturating_add(payload.len()));
encoded.extend_from_slice(×tamp.to_be_bytes());
encoded.extend_from_slice(payload);
encoded
}
fn decode_value(value: &[u8]) -> Result<(Timestamp, Vec<u8>), ApiError> {
let header: [u8; TS_WIDTH] = value
.get(..TS_WIDTH)
.and_then(|bytes| bytes.try_into().ok())
.ok_or_else(|| {
ApiError::CorruptEvent(format!(
"event value shorter than timestamp header: {value:?}"
))
})?;
let timestamp = u64::from_be_bytes(header);
let payload = value.get(TS_WIDTH..).unwrap_or_default().to_vec();
Ok((timestamp, payload))
}
#[cfg(test)]
#[path = "event_store_tests.rs"]
mod tests;