mod runtime;
use super::{invalid, KeyValueDiskANNCanonical, RetainedDiskANNCanonical};
use crate::diskann_index::{
catalog::{DiskANNIndexResolver, DiskANNIndexScope},
format::DiskANNVectorVersion,
pages::DiskANNReadLimits,
RetainedDiskANNIndex,
};
use crate::key_value::{catalog::diskann::Binding, publication::selected_generation};
use crate::{
read_control::StorageReadControl, vector_index::DiskANNIndexParams, RelationIdentity,
StorageBackendResult,
};
use std::sync::Arc;
use uqa_core::{memory::MemoryReservation, DocId};
pub struct KeyValueDiskANNHandle {
canonical: KeyValueDiskANNCanonical,
index: RelationIdentity,
resolver: Arc<dyn DiskANNIndexResolver + Send + Sync>,
scope: DiskANNIndexScope,
parameters: DiskANNIndexParams,
limits: DiskANNReadLimits,
control: StorageReadControl,
_memory: MemoryReservation,
}
impl KeyValueDiskANNCanonical {
pub fn bind(
self,
index: RelationIdentity,
resolver: Arc<dyn DiskANNIndexResolver + Send + Sync>,
limits: DiskANNReadLimits,
control: &StorageReadControl,
) -> StorageBackendResult<KeyValueDiskANNHandle> {
control.check()?;
let bytes = [
size_of::<KeyValueDiskANNHandle>(),
self.index.table.capacity(),
self.index.field.capacity(),
index.schema.capacity(),
index.name.capacity(),
]
.into_iter()
.try_fold(0_usize, usize::checked_add)
.ok_or(uqa_core::memory::MemoryError::SizeOverflow)?;
let memory = control.memory().reserve(bytes)?;
self.initialize_populations(&index, &*resolver, control)?;
let source = self.retain_for_index(&index, control)?;
let scope = source.index_scope(&*resolver, control)?;
let parameters = source
.index_parameters()
.ok_or_else(|| invalid("live index has no bound parameters"))?;
source
.selected_source(&*resolver, control)?
.ok_or_else(|| invalid("live index has no published generation"))?;
control.check()?;
Ok(KeyValueDiskANNHandle {
canonical: self,
index,
resolver,
scope,
parameters,
limits,
control: control.clone(),
_memory: memory,
})
}
}
impl KeyValueDiskANNHandle {
pub fn replace(
&self,
document: DocId,
vectors: &[Vec<f32>],
) -> StorageBackendResult<DiskANNVectorVersion> {
self.control.check()?;
self.canonical
.replace_guarded(document, vectors, &self.control, &mut |read, batch| {
let canonical = &self.canonical.index;
let binding = Binding::capture(
read,
&canonical.table,
&canonical.field,
canonical.dimensions,
&self.index,
&self.control,
)?;
let current = binding.scope(read, &*self.resolver, &self.control, &self.control)?;
self.validate(¤t, binding.parameters)?;
if selected_generation(¤t, read, &self.control)?.is_none() {
return Err(invalid("live index has no published generation"));
}
binding.require_current(read, batch, &self.control)
})
}
pub fn snapshot(&self) -> StorageBackendResult<RetainedDiskANNIndex<RetainedDiskANNCanonical>> {
self.retain_current()?
.into_vector_index(&*self.resolver, self.limits, &self.control)?
.ok_or_else(|| invalid("live index has no published generation"))
}
fn retain_current(&self) -> StorageBackendResult<RetainedDiskANNCanonical> {
self.control.check()?;
let source = self
.canonical
.retain_for_index(&self.index, &self.control)?;
let current = source.index_scope(&*self.resolver, &self.control)?;
self.validate(
¤t,
source
.index_parameters()
.ok_or_else(|| invalid("live index has no bound parameters"))?,
)?;
Ok(source)
}
fn validate(
&self,
current: &DiskANNIndexScope,
parameters: DiskANNIndexParams,
) -> StorageBackendResult<()> {
self.scope.require_same_index(current, &self.control)?;
if self.parameters != parameters {
return Err(invalid("live index parameters changed"));
}
self.control.check()
}
}