use std::path::Path;
use super::{
invalid, DiskANNBuildInput, DiskANNBuildSink, DiskANNGenerationOptions, DiskANNMergedGraph,
DiskANNTemporaryBudget,
};
use crate::diskann_index::{
format::{
DiskANNBuildCoverage, DiskANNChangeIdentity, DiskANNGeneration, DiskANNManifest,
DiskANNOriginSummary,
},
DiskANNCanonicalRead,
};
use crate::{read_control::StorageReadControl, StorageBackendResult};
mod origins;
#[cfg(test)]
mod tests;
pub struct DiskANNBuildCapture<S> {
input: DiskANNBuildInput,
source: S,
origins: origins::Origins,
}
impl<S: DiskANNCanonicalRead> DiskANNBuildCapture<S> {
pub fn capture(
generation: DiskANNGeneration,
source: S,
directory: &Path,
temporary: &DiskANNTemporaryBudget,
control: &StorageReadControl,
) -> StorageBackendResult<Self> {
source.check_control(control)?;
let mut origins = origins::Origins::new(directory, temporary, control)?;
let input = DiskANNBuildInput::capture(
generation,
source.dimensions(),
directory,
temporary,
control,
|visit| origins.capture(&source, control, visit),
)?;
source.check_control(control)?;
Ok(Self {
input,
source,
origins,
})
}
pub fn input(&self) -> &DiskANNBuildInput {
&self.input
}
pub fn write_generation(
&self,
graph: &DiskANNMergedGraph,
options: DiskANNGenerationOptions,
sink: &mut dyn DiskANNBuildSink,
) -> StorageBackendResult<DiskANNManifest> {
let control = &self.input.control;
self.source.check_control(control)?;
control.check_value_size(DiskANNManifest::MAX_ENCODED_BYTES, options.max_record_bytes)?;
self.origins.check_record_limit(
self.input.coverage().generation(),
self.input.dimensions(),
options.max_record_bytes,
control,
)?;
let manifest = self.input.write_generation(graph, options, sink)?;
self.origins.write(
self.input.coverage().generation(),
self.input.dimensions(),
sink,
options.max_record_bytes,
control,
)?;
self.source.check_control(control)?;
manifest.with_origins(self.origins.summary())
}
pub fn finish(
self,
manifest: &DiskANNManifest,
control: &StorageReadControl,
) -> StorageBackendResult<DiskANNCanonicalCoverage<S>> {
self.input.control.check()?;
self.source.check_control(control)?;
let actual = manifest.input();
if actual.coverage != self.input.coverage()
|| actual.nodes != self.input.node_count()
|| actual.side_vectors != self.input.side_count()
|| manifest
.origins()
.is_some_and(|origins| origins != self.origins.summary())
{
return Err(invalid(
"manifest does not match the retained build capture",
));
}
control.check()?;
Ok(DiskANNCanonicalCoverage {
source: self.source,
fingerprint: self.input.coverage(),
control: self.input.control.clone(),
origins: self.origins.summary(),
manifest: *manifest,
})
}
}
pub struct DiskANNCanonicalCoverage<S> {
source: S,
fingerprint: DiskANNBuildCoverage,
control: StorageReadControl,
origins: DiskANNOriginSummary,
manifest: DiskANNManifest,
}
impl<S: DiskANNCanonicalRead> DiskANNCanonicalCoverage<S> {
pub fn check_control(&self, control: &StorageReadControl) -> StorageBackendResult<()> {
self.control.check()?;
self.source.check_control(control)
}
pub fn manifest(&self) -> &DiskANNManifest {
&self.manifest
}
pub fn fingerprint(&self) -> DiskANNBuildCoverage {
self.fingerprint
}
pub fn origins(&self) -> DiskANNOriginSummary {
self.origins
}
pub fn source(&self) -> &S {
&self.source
}
pub fn contains(
&self,
change: DiskANNChangeIdentity,
control: &StorageReadControl,
) -> StorageBackendResult<bool> {
self.control.check()?;
self.source.check_control(control)?;
let actual = self.source.origin(change.document(), control)?;
self.control.check()?;
self.source.check_control(control)?;
Ok(actual == Some(change.version()))
}
}