use std::collections::BTreeMap;
use tracing::warn;
use crate::archive::{CallerRow, SourceMeta};
use crate::error::{Error, Result};
use crate::writer::{SourceWriter, Writer};
use super::frame::{Frame, IndexKind, IndexState, NO_INDEX_STATE};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Applied {
pub rows: usize,
pub rows_skipped: usize,
pub segments: usize,
pub segments_held: usize,
pub index_entries: usize,
pub clock_offsets: usize,
pub sources: usize,
pub gap: bool,
}
struct SourceState {
writer: SourceWriter,
index_state: IndexState,
seen_full: bool,
last_seq: Option<u64>,
complete: bool,
last_offset: Option<(i64, i64)>,
logged_gap: bool,
}
pub struct Subscriber {
writer: Writer,
sources: BTreeMap<u32, SourceState>,
}
impl std::fmt::Debug for Subscriber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Subscriber")
.field("path", &self.writer.path())
.field("sources", &self.sources.len())
.finish_non_exhaustive()
}
}
impl Subscriber {
pub fn new(writer: Writer) -> Self {
Subscriber {
writer,
sources: BTreeMap::new(),
}
}
pub fn apply(&mut self, frame: Frame) -> Result<Applied> {
match frame {
Frame::Handshake {
source,
uuid,
labels,
metadata,
clock_anchor_wall_ns,
complete,
} => {
let writer = self.writer.add_source_with_uuid(
SourceMeta {
labels,
metadata,
clock_anchor_wall_ns,
},
uuid.as_deref(),
)?;
self.sources.insert(
source,
SourceState {
writer,
index_state: NO_INDEX_STATE,
seen_full: false,
last_seq: None,
complete,
last_offset: None,
logged_gap: false,
},
);
Ok(Applied {
sources: 1,
..Applied::default()
})
}
Frame::Index {
source,
stream,
ts,
kind,
state,
blob,
} => {
let st = Self::source_mut(&mut self.sources, source)?;
st.writer
.caller_rows(stream, vec![CallerRow { ts, blob }])?;
st.index_state = state;
if kind == IndexKind::Full {
st.seen_full = true;
}
Ok(Applied {
index_entries: 1,
..Applied::default()
})
}
Frame::Rows {
source,
seq,
index_state,
rows,
} => {
let st = Self::source_mut(&mut self.sources, source)?;
let gap = match st.last_seq {
Some(last) => seq > last.saturating_add(1),
None => false,
};
st.last_seq = Some(seq);
if gap && !st.logged_gap {
st.logged_gap = true;
warn!(
"source {source}: a replication frame was lost (seq jumped to {seq}); \
the rows that arrived are still good, and this is logged once"
);
}
let unresolvable = index_state != NO_INDEX_STATE && !st.seen_full;
if unresolvable || index_state != st.index_state {
return Ok(Applied {
rows_skipped: rows.len(),
gap,
..Applied::default()
});
}
let n = rows.len();
if n > 0 {
st.writer.wal(rows)?;
}
Ok(Applied {
rows: n,
gap,
..Applied::default()
})
}
Frame::Segment {
source,
stream,
meta,
bytes,
caller_index,
} => {
let st = Self::source_mut(&mut self.sources, source)?;
let inserted =
st.writer
.adopt_segment(&stream, &meta, &bytes, caller_index.as_deref())?;
Ok(Applied {
segments: usize::from(inserted),
segments_held: usize::from(!inserted),
..Applied::default()
})
}
Frame::ClockOffset {
source,
ts,
offset_ns,
} => {
let st = Self::source_mut(&mut self.sources, source)?;
st.writer.clock_offset(ts, offset_ns)?;
st.last_offset = Some((ts, offset_ns));
Ok(Applied {
clock_offsets: 1,
..Applied::default()
})
}
}
}
pub fn apply_all(&mut self, frames: impl IntoIterator<Item = Frame>) -> Result<Applied> {
let mut total = Applied::default();
for frame in frames {
let one = self.apply(frame)?;
total.rows += one.rows;
total.rows_skipped += one.rows_skipped;
total.segments += one.segments;
total.segments_held += one.segments_held;
total.index_entries += one.index_entries;
total.clock_offsets += one.clock_offsets;
total.sources += one.sources;
total.gap |= one.gap;
}
Ok(total)
}
pub fn seal(&mut self, source: u32, streams: Vec<String>) -> Result<()> {
Self::source_mut(&mut self.sources, source)?
.writer
.seal(streams)
}
pub fn sync(&mut self) -> Result<()> {
for st in self.sources.values_mut() {
st.writer.sync()?;
}
Ok(())
}
pub fn finish(mut self) -> Result<()> {
for (_, st) in std::mem::take(&mut self.sources) {
let SourceState {
writer,
complete,
last_offset,
..
} = st;
if complete {
writer.finalize(last_offset.unwrap_or((0, 0)))?;
} else {
drop(writer);
}
}
self.writer.join()
}
pub fn path(&self) -> &std::path::Path {
self.writer.path()
}
fn source_mut(
sources: &mut BTreeMap<u32, SourceState>,
source: u32,
) -> Result<&mut SourceState> {
sources.get_mut(&source).ok_or_else(|| {
Error::Message(format!(
"replication frame for source {source}, which no handshake has introduced; \
every frame names a source by the ordinal its handshake assigned"
))
})
}
}