use std::num::NonZeroUsize;
use haematite::branch::Timestamp;
use haematite::branch::history::{Key, Row};
use haematite::db::ReadOnlyDatabase;
use haematite::tree::Hash;
use serde::{Deserialize, Serialize};
use super::{BranchRefRecord, Failure, SERVICE, Storage, branch_name};
use crate::serve::protocol::Version;
const DEAD_PREFIX: &[u8] = b"dead-generation\0";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Dead {
pub branch: String,
pub last: Version,
pub discarded_at: Timestamp,
}
pub(super) fn dead_key(branch: &str, generation: u64) -> Vec<u8> {
let mut key = name_prefix(branch);
key.extend_from_slice(&generation.to_be_bytes());
key
}
fn name_prefix(branch: &str) -> Vec<u8> {
let mut key = Vec::with_capacity(DEAD_PREFIX.len() + 32 + 8);
key.extend_from_slice(DEAD_PREFIX);
key.extend_from_slice(Hash::of(branch.as_bytes()).as_bytes());
key
}
fn past(prefix: &[u8]) -> Vec<u8> {
let mut end = prefix.to_vec();
if let Some(last) = end.last_mut() {
*last = last.saturating_add(1);
}
end
}
fn page(
reader: &ReadOnlyDatabase,
service: &BranchRefRecord,
(start, end): (&[u8], &[u8]),
limit: NonZeroUsize,
) -> Result<Vec<(Vec<u8>, Dead)>, Failure> {
let mut found = Vec::new();
for shard in &service.shards {
let read = reader
.range_page_at(shard.shard_id, shard.head, start, end, limit)
.map_err(Failure::observer)?;
for (key, bytes) in read.entries {
let dead = serde_json::from_slice(&bytes)
.map_err(|error| Failure::new("dead_generation_corrupt", error.to_string()))?;
found.push((key, dead));
}
}
found.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
Ok(found)
}
pub(super) fn of_name(
reader: &ReadOnlyDatabase,
service: &BranchRefRecord,
branch: &str,
limit: NonZeroUsize,
) -> Result<Vec<Dead>, Failure> {
let start = name_prefix(branch);
let end = past(&start);
let mut found = page(reader, service, (&start, &end), limit)?;
found.truncate(limit.get());
Ok(found.into_iter().map(|(_key, dead)| dead).collect())
}
pub(super) fn marker(
reader: &ReadOnlyDatabase,
service: &BranchRefRecord,
branch: &str,
generation: u64,
) -> Result<Option<Dead>, Failure> {
#[cfg(test)]
super::history::MARKER_READS.with(|reads| reads.set(reads.get() + 1));
let key = dead_key(branch, generation);
let end = past(&key);
let found = page(reader, service, (&key, &end), NonZeroUsize::MIN)?;
Ok(found
.into_iter()
.find(|(found, _dead)| *found == key)
.map(|(_key, dead)| dead))
}
pub(super) fn discard_row(branch: &str, at: Version, written: Timestamp) -> Row {
Row {
key: Key {
branch: Hash::of(branch.as_bytes()),
generation: at.generation,
sequence: at.sequence,
},
timestamp: written,
shards: Vec::new(),
}
}
pub(super) fn ended(
reader: &ReadOnlyDatabase,
service: &BranchRefRecord,
branch: &str,
taken: &[Version],
) -> Result<Vec<Vec<u8>>, Failure> {
let mut generations: Vec<u64> = taken.iter().map(|at| at.generation).collect();
generations.dedup();
let mut ended = Vec::new();
for generation in generations {
if let Some(dead) = marker(reader, service, branch, generation)?
&& taken.contains(&dead.last)
{
ended.push(dead_key(branch, generation));
}
}
Ok(ended)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeadPage {
pub names: Vec<String>,
pub last: Option<Vec<u8>>,
pub end: bool,
}
impl Storage {
pub fn dead_page(
&self,
after: Option<&[u8]>,
limit: NonZeroUsize,
) -> Result<DeadPage, Failure> {
let service = self
.refs
.get(SERVICE)
.ok_or_else(|| Failure::new("reference_missing", "missing service namespace"))?;
let start = after.map_or_else(
|| DEAD_PREFIX.to_vec(),
|key| {
let mut start = key.to_vec();
start.push(0);
start
},
);
let read = NonZeroUsize::new(limit.get().saturating_add(1)).unwrap_or(NonZeroUsize::MAX);
let mut found = page(&self.reader, service, (&start, &past(DEAD_PREFIX)), read)?;
let end = found.len() <= limit.get();
found.truncate(limit.get());
let last = found.last().map(|(key, _dead)| key.clone());
let mut names: Vec<String> = found
.into_iter()
.map(|(_key, dead)| dead.branch)
.filter(|name| self.refs.get(&branch_name(name)).is_none())
.collect();
names.dedup();
Ok(DeadPage { names, last, end })
}
}