use super::ledger::FragReuseLedger;
use super::metadata::is_tagged;
use crate::format::IndexMetadata;
use lance_core::{Error, Result};
use roaring::RoaringBitmap;
use std::collections::HashSet;
use std::sync::Arc;
#[derive(Debug)]
pub struct TaggedLineage {
fragments: Option<RoaringBitmap>,
ledger: Option<Arc<FragReuseLedger>>,
}
impl TaggedLineage {
pub fn new(entry: &IndexMetadata, ledger: Option<Arc<FragReuseLedger>>) -> Self {
Self {
fragments: entry.fragment_bitmap.clone(),
ledger: ledger.filter(|ledger| !ledger.has_unsupported_transitions()),
}
}
pub fn from_indices_with_ledger(
indices: &[IndexMetadata],
ledger: Option<Arc<FragReuseLedger>>,
) -> Result<Option<Self>> {
let Some(entry) = indices.iter().find(|index| is_tagged(index)) else {
return Ok(None);
};
let ledger = match ledger {
Some(ledger) => ledger,
None => {
let details = entry.index_details.as_ref().ok_or_else(|| {
Error::invalid_input(
"the tagged fragment reuse entry carries no details; its history \
cannot be read",
)
})?;
FragReuseLedger::decode_inline(entry.index_version, details)?
.map(Arc::new)
.ok_or_else(|| {
Error::invalid_input(
"the tagged fragment reuse history is stored externally and was \
not supplied to the manifest build; commit through the dataset \
so the history is decoded first",
)
})?
}
};
Ok(Some(Self::new(entry, Some(ledger))))
}
pub fn has_ledger(&self) -> bool {
self.ledger.is_some()
}
pub fn mentions(&self, fragment: u32) -> Option<bool> {
self.ledger
.as_ref()
.map(|ledger| ledger.contains_fragment(fragment))
}
pub fn contains(&self, fragment: u32) -> bool {
self.fragments
.as_ref()
.is_none_or(|fragments| fragments.contains(fragment))
}
pub fn immediate_destinations_of(&self, source: u32) -> Option<Vec<u32>> {
let ledger = self.ledger.as_ref()?;
let Some(position) = ledger.consumer(source) else {
return Some(Vec::new());
};
ledger.transitions()[position]
.destinations()
.iter()
.map(|digest| u32::try_from(digest.id).ok())
.collect()
}
pub fn destinations_of(&self, source: u32) -> Option<Vec<u32>> {
let ledger = self.ledger.as_ref()?;
let mut reached = Vec::new();
let mut seen = HashSet::new();
let mut pending = vec![source];
while let Some(fragment) = pending.pop() {
let Some(position) = ledger.consumer(fragment) else {
continue;
};
for digest in ledger.transitions()[position].destinations() {
let destination = u32::try_from(digest.id).ok()?;
if seen.insert(destination) {
reached.push(destination);
pending.push(destination);
}
}
}
Some(reached)
}
}