use crate::error::SlateDBError;
use crate::manifest::store::FenceableManifest;
use crate::{CloseReason, ErrorKind, RowEntry, VersionedManifest};
use async_trait::async_trait;
use futures::future::BoxFuture;
use object_store::path::Path;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::ops::{Bound, Range, RangeFrom};
use std::sync::Arc;
use std::time::Duration;
pub(crate) mod slatedb;
#[cfg(test)]
pub(crate) mod test_utils;
pub(crate) mod wal_disabled;
pub use crate::wal::slatedb::reader::{
SlateDbWalReader, SlateDbWalReaderBuilder, SlateDbWalReaderOptions,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalFileRange(pub Bound<u64>, pub Bound<u64>);
impl From<Range<u64>> for WalFileRange {
fn from(range: Range<u64>) -> Self {
WalFileRange(Bound::Included(range.start), Bound::Excluded(range.end))
}
}
impl From<RangeFrom<u64>> for WalFileRange {
fn from(range: RangeFrom<u64>) -> Self {
WalFileRange(Bound::Included(range.start), Bound::Unbounded)
}
}
impl TryFrom<WalFileRange> for Range<u64> {
type Error = ();
fn try_from(range: WalFileRange) -> Result<Self, Self::Error> {
match (range.0, range.1) {
(Bound::Included(start), Bound::Excluded(end)) => Ok(start..end),
_ => Err(()),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum WalError {
Fenced,
WalTruncated(u64),
Closed,
Unavailable(Arc<dyn Error + Sync + Send + 'static>),
DataError(Arc<dyn Error + Sync + Send + 'static>),
InternalError(Arc<dyn Error + Sync + Send + 'static>),
}
impl Display for WalError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WalError::Fenced => write!(f, "WAL writer was fenced"),
WalError::WalTruncated(wal_id) => write!(f, "WAL was truncated at file {}", *wal_id),
WalError::Closed => write!(f, "WAL is closed"),
WalError::Unavailable(source) => write!(f, "WAL is unavailable: {source}"),
WalError::DataError(source) => write!(f, "WAL data error: {source}"),
WalError::InternalError(source) => write!(f, "WAL internal error: {source}"),
}
}
}
impl Error for WalError {}
pub struct WriterManifest {
manifest: FenceableManifest,
}
impl From<WriterManifest> for FenceableManifest {
fn from(manifest: WriterManifest) -> Self {
manifest.manifest
}
}
impl From<FenceableManifest> for WriterManifest {
fn from(manifest: FenceableManifest) -> Self {
WriterManifest { manifest }
}
}
impl WriterManifest {
pub fn manifest(&self) -> VersionedManifest {
let (id, manifest) = self.manifest.manifest();
VersionedManifest::from_manifest(id, manifest.clone())
}
pub fn replay_after_wal_id(&self) -> u64 {
self.manifest().core().replay_after_wal_id
}
pub fn epoch(&self) -> u64 {
self.manifest().writer_epoch()
}
pub async fn refresh(&mut self) -> Result<(), WalError> {
self.manifest.refresh().await?;
Ok(())
}
}
pub struct WriterInitResult {
pub replay_iterator: Box<dyn WalIterator>,
pub wal_writer: Box<dyn WalWriter>,
}
#[async_trait]
pub trait WriterInit: Send + Sync + 'static {
async fn fence_and_init(
&self,
manifest: &mut WriterManifest,
) -> Result<WriterInitResult, WalError>;
}
#[derive(Debug, Clone)]
pub struct WalStatus {
pub closed_reason: Option<WalError>,
pub estimated_bytes: usize,
pub last_flushed_wal_id: u64,
pub last_flushed_seq: Option<u64>,
#[allow(dead_code)]
pub buffered_wal_entries_count: usize,
}
#[derive(Debug, Clone)]
pub enum WalEvent {
WalFlushed(WalStatus),
WalClosed(WalStatus),
}
pub type WalStatusListener = Arc<dyn Fn(WalEvent) + Send + Sync + 'static>;
#[async_trait]
pub trait WalObserver: Send + Sync + 'static {
fn status(&self) -> Result<WalStatus, WalStatus>;
fn subscribe(&self, listener: WalStatusListener) -> Result<(), WalError>;
}
pub type FlushResultFuture = BoxFuture<'static, Result<(), WalError>>;
#[async_trait]
pub trait WalWriter: Send {
async fn append(&mut self, write_batch: &[RowEntry]) -> Result<(), WalError>;
async fn flush(&mut self) -> Result<FlushResultFuture, WalError>;
fn should_flush_memtable(&self, _replay_after_wal_id: u64) -> bool {
false
}
fn observer(&self) -> Box<dyn WalObserver>;
fn status(&self) -> Result<WalStatus, WalStatus>;
async fn close(&mut self) -> Result<(), WalError>;
}
#[derive(Clone)]
pub struct WalRows {
pub rows: Vec<RowEntry>,
pub last_consumed_wal_file_id: u64,
}
#[async_trait]
pub trait WalIterator: Send + 'static {
async fn next(&mut self) -> Result<Option<WalRows>, WalError>;
}
#[async_trait]
pub trait WalReader: Send + Sync + 'static {
async fn iterator(
&self,
wal_file_id_range: WalFileRange,
) -> Result<Box<dyn WalIterator>, WalError>;
async fn last_wal_file_id(&self, replay_after_wal_id: u64) -> Result<u64, WalError>;
}
#[async_trait]
pub trait WalGc: Send + Sync + 'static {
async fn collect(
&self,
referenced_ranges: Vec<WalFileRange>,
min_age: Duration,
dry_run: bool,
) -> Result<(), WalError>;
}
#[async_trait]
pub trait WalAdmin: Send + Sync + 'static {
fn garbage_collector(&self, path: &Path) -> Arc<dyn WalGc>;
async fn delete_wal(&self, path: &Path, dry_run: bool) -> Result<Vec<String>, WalError>;
async fn is_empty(
&self,
path: &Path,
replay_after_wal_id: u64,
wal_id_last_seen: u64,
) -> Result<bool, WalError>;
async fn clone_wal(
&self,
from_path: &Path,
from_manifest: VersionedManifest,
to_path: &Path,
) -> Result<(u64, u64), WalError>;
}
impl From<WalStatus> for WalError {
fn from(status: WalStatus) -> Self {
status
.closed_reason
.expect("unexpected conversion of wal status with no error")
}
}
impl From<WalStatus> for SlateDBError {
fn from(status: WalStatus) -> Self {
WalError::from(status).into()
}
}
impl From<SlateDBError> for WalError {
fn from(value: SlateDBError) -> Self {
let public: crate::Error = value.clone().into();
match public.kind() {
ErrorKind::Closed(CloseReason::Fenced) => WalError::Fenced,
ErrorKind::Closed(CloseReason::Clean) => WalError::Closed,
ErrorKind::Closed(_) => WalError::InternalError(Arc::new(value)),
ErrorKind::Unavailable => WalError::Unavailable(Arc::new(value)),
ErrorKind::Invalid => WalError::InternalError(Arc::new(value)),
ErrorKind::Data => WalError::DataError(Arc::new(value)),
ErrorKind::Internal => WalError::InternalError(Arc::new(value)),
ErrorKind::Transaction => WalError::InternalError(Arc::new(value)),
}
}
}
impl From<WalError> for SlateDBError {
fn from(value: WalError) -> Self {
match value {
WalError::Fenced => SlateDBError::Fenced,
WalError::WalTruncated(wal_id) => SlateDBError::WalTruncated(wal_id),
WalError::Closed => SlateDBError::Closed,
WalError::Unavailable(err) => SlateDBError::WalUnavailable(err),
WalError::DataError(err) => SlateDBError::WalDataError(err),
WalError::InternalError(err) => SlateDBError::WalInternalError(err),
}
}
}
#[cfg(test)]
mod tests {
use super::WalError;
use std::sync::Arc;
#[test]
fn wal_error_display() {
let source = || {
Arc::new(std::io::Error::other("source error"))
as Arc<dyn std::error::Error + Send + Sync + 'static>
};
assert_eq!(WalError::Fenced.to_string(), "WAL writer was fenced");
assert_eq!(
WalError::WalTruncated(123).to_string(),
"WAL was truncated at file 123"
);
assert_eq!(WalError::Closed.to_string(), "WAL is closed");
assert_eq!(
WalError::Unavailable(source()).to_string(),
"WAL is unavailable: source error"
);
assert_eq!(
WalError::DataError(source()).to_string(),
"WAL data error: source error"
);
assert_eq!(
WalError::InternalError(source()).to_string(),
"WAL internal error: source error"
);
}
}