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 std::error::Error;
use std::fmt::{Display, Formatter};
use std::ops::{Bound, Range};
use std::sync::Arc;
#[cfg(test)]
pub(crate) mod test_utils;
pub(crate) mod wal_disabled;
pub(crate) mod wal_sst_builder;
pub(crate) mod writer_init;
pub struct WalFileRange(Bound<u64>, Bound<u64>);
impl From<Range<u64>> for WalFileRange {
fn from(range: Range<u64>) -> Self {
WalFileRange(Bound::Included(range.start), Bound::Excluded(range.end))
}
}
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,
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 => write!(f, "WAL was truncated"),
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_range: WalFileRange,
pub wal_writer: Box<dyn WalWriter>,
}
#[async_trait]
pub trait WriterInit {
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 observer(&self) -> Box<dyn WalObserver>;
fn status(&self) -> Result<WalStatus, WalStatus>;
async fn close(&mut self) -> Result<(), 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 => SlateDBError::WalTruncated,
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.to_string(), "WAL was truncated");
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"
);
}
}