use std::num::NonZeroUsize;
use super::embedded::{self, Field};
use super::{BranchRefRecord, Captured, Failure, Storage, branch_name, identifier, reads, version};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pruned {
pub removed: usize,
pub more: bool,
}
const FAMILIES: [(&[u8], &[u8]); 2] = [(b"v\0", b"v\x01"), (b"vw\0", b"vw\x01")];
impl Storage {
pub fn embed_prune(
&mut self,
branch: &str,
page_records: NonZeroUsize,
) -> Result<Pruned, Failure> {
identifier(branch)?;
let record = self
.refs
.get(&branch_name(branch))
.ok_or_else(|| Failure::new("branch_missing", branch))?;
let head = Captured {
branch: branch.to_owned(),
version: version(record),
shards: record.shards.clone(),
};
let name = embedded::companion_name(branch);
let nothing = Pruned {
removed: 0,
more: false,
};
let Some(companion) = self.refs.get(&name) else {
return Ok(nothing);
};
let positions = self.companion_positions(companion)?;
let kept = |collection: &str, field: &str| -> Result<bool, Failure> {
Ok(
positions.contains_key(&(collection.to_owned(), field.to_owned()))
|| self.declares(&head, collection, field)?,
)
};
let mut found = None;
for (index, family) in FAMILIES.into_iter().enumerate() {
let scan = Scan {
name: &name,
companion,
family,
};
if let Some(FamilyPage { removals, more }) =
family_page(self, &scan, page_records, &kept)?
{
let (from, to) = FAMILIES[1];
let windows = index == 0 && first_key(self, companion, from, to)?.is_some();
found = Some((removals, more || windows));
break;
}
}
let Some((removals, more)) = found else {
return Ok(nothing);
};
let writes: Vec<(Vec<u8>, Option<Vec<u8>>)> =
removals.into_iter().map(|key| (key, None)).collect();
let removed = writes.len();
if removed == 0 {
return Ok(nothing);
}
self.commit_namespace(&name, &writes)?;
Ok(Pruned { removed, more })
}
fn declares(&self, head: &Captured, collection: &str, field: &str) -> Result<bool, Failure> {
let key = super::embed_job::field_key(collection, field);
match reads::get_bytes(&self.reader, head, &key)? {
Some(bytes) => Ok(matches!(embedded::read_field(&bytes)?, Field::Embedded(_))),
None => Ok(false),
}
}
}
struct Scan<'a> {
name: &'a str,
companion: &'a BranchRefRecord,
family: (&'static [u8], &'static [u8]),
}
struct FamilyPage {
removals: Vec<Vec<u8>>,
more: bool,
}
fn family_page(
storage: &Storage,
scan: &Scan<'_>,
page_records: NonZeroUsize,
kept: &impl Fn(&str, &str) -> Result<bool, Failure>,
) -> Result<Option<FamilyPage>, Failure> {
let (family, family_end) = scan.family;
let mut from = family.to_vec();
loop {
let Some(key) = first_key(storage, scan.companion, &from, family_end)? else {
return Ok(None);
};
let (collection, field) = vector_field(scan.name, family, &key)?;
let mut prefix = family.to_vec();
prefix.extend_from_slice(format!("{collection}\0{field}\0").as_bytes());
let end = past(&prefix);
if kept(&collection, &field)? {
from = end;
continue;
}
let (removals, more) = field_page(storage, scan.companion, &prefix, &end, page_records)?;
let later = first_key(storage, scan.companion, &end, family_end)?.is_some();
return Ok(Some(FamilyPage {
removals,
more: more || later,
}));
}
}
fn first_key(
storage: &Storage,
companion: &BranchRefRecord,
from: &[u8],
to: &[u8],
) -> Result<Option<Vec<u8>>, Failure> {
let mut first: Option<Vec<u8>> = None;
for shard in &companion.shards {
let page = storage
.reader
.range_page_at(shard.shard_id, shard.head, from, to, NonZeroUsize::MIN)
.map_err(Failure::observer)?;
if let Some((key, _value)) = page.entries.into_iter().next()
&& first.as_ref().is_none_or(|known| key < *known)
{
first = Some(key);
}
}
Ok(first)
}
fn field_page(
storage: &Storage,
companion: &BranchRefRecord,
prefix: &[u8],
end: &[u8],
limit: NonZeroUsize,
) -> Result<(Vec<Vec<u8>>, bool), Failure> {
let mut keys = Vec::new();
let mut more = false;
for shard in &companion.shards {
let page = storage
.reader
.range_page_at(shard.shard_id, shard.head, prefix, end, limit)
.map_err(Failure::observer)?;
more |= page.next_from.is_some();
keys.extend(page.entries.into_iter().map(|(key, _value)| key));
}
keys.sort();
if keys.len() > limit.get() {
keys.truncate(limit.get());
more = true;
}
Ok((keys, more))
}
fn vector_field(companion: &str, family: &[u8], key: &[u8]) -> Result<(String, String), Failure> {
let corrupt = || {
Failure::new(
"embed_vector_key_corrupt",
format!("{companion} holds a vector key that names no collection, field and record"),
)
};
let named = key.strip_prefix(family).ok_or_else(corrupt)?;
let mut parts = named.splitn(3, |byte| *byte == 0);
let (Some(collection), Some(field), Some(_record)) = (parts.next(), parts.next(), parts.next())
else {
return Err(corrupt());
};
let text = |part: &[u8]| {
std::str::from_utf8(part)
.map(str::to_owned)
.map_err(|_error| corrupt())
};
Ok((text(collection)?, text(field)?))
}
fn past(prefix: &[u8]) -> Vec<u8> {
let mut end = prefix.to_vec();
if let Some(last) = end.last_mut() {
*last = 1;
}
end
}
#[cfg(test)]
#[path = "embed_prune_tests.rs"]
mod tests;