use std::sync::Arc;
use ailake_core::{AilakeError, AilakeResult};
use ailake_file::{parquet_footer_start, AilakeHeader, Precision, FLAG_INDEX_IVF_PQ, HEADER_SIZE};
use ailake_index::{AnyIndex, IvfPqSerializer, MmapLoader};
use ailake_parquet::ParquetVectorReader;
use ailake_store::Store;
use bytes::Bytes;
const SPECULATIVE_TAIL_BYTES: u64 = 65_536;
struct TailBuf {
base: u64,
bytes: Bytes,
}
impl TailBuf {
fn slice(&self, start: u64, end: u64) -> Option<Bytes> {
if start < self.base || end > self.base + self.bytes.len() as u64 {
return None;
}
let rel_start = (start - self.base) as usize;
let rel_end = (end - self.base) as usize;
Some(self.bytes.slice(rel_start..rel_end))
}
}
pub async fn load_primary_index(store: &Arc<dyn Store>, path: &str) -> AilakeResult<AnyIndex> {
let file_size = store.file_size(path).await?;
let (ailk_offset, tail) = resolve_ailk_offset(store, path, file_size).await?;
let header_end = ailk_offset
.checked_add(HEADER_SIZE as u64)
.ok_or(AilakeError::NotAnAilakeFile)?;
let header_bytes = match tail.slice(ailk_offset, header_end) {
Some(b) => b,
None => store.get_range(path, ailk_offset..header_end).await?,
};
let header_arr: [u8; HEADER_SIZE] = header_bytes
.as_ref()
.try_into()
.map_err(|_| AilakeError::NotAnAilakeFile)?;
let header = AilakeHeader::from_bytes(&header_arr)?;
let index_start = ailk_offset
.checked_add(header.hnsw_offset)
.ok_or(AilakeError::NotAnAilakeFile)?;
let index_end = index_start
.checked_add(header.hnsw_len)
.ok_or(AilakeError::NotAnAilakeFile)?;
let index_bytes = match tail.slice(index_start, index_end) {
Some(b) => b,
None => store.get_range(path, index_start..index_end).await?,
};
if header.flags & FLAG_INDEX_IVF_PQ != 0 {
let idx = IvfPqSerializer::from_bytes(&index_bytes)?;
Ok(AnyIndex::IvfPq(idx))
} else {
let mut idx = MmapLoader::from_bytes(&index_bytes)?;
if header.precision == Precision::F16 {
idx.quantize_to_f16();
}
Ok(AnyIndex::Hnsw(idx))
}
}
async fn resolve_ailk_offset(
store: &Arc<dyn Store>,
path: &str,
file_size: u64,
) -> AilakeResult<(u64, TailBuf)> {
let guess_len = SPECULATIVE_TAIL_BYTES.min(file_size);
let mut base = file_size - guess_len;
let mut tail = store.get_range(path, base..file_size).await?;
if parquet_footer_start(&tail).is_err() {
if tail.len() < 8 {
return Err(AilakeError::NotAnAilakeFile);
}
let footer_thrift_len =
u32::from_le_bytes(tail[tail.len() - 8..tail.len() - 4].try_into().unwrap()) as u64;
let exact_len = (8 + footer_thrift_len).min(file_size);
base = file_size - exact_len;
tail = store.get_range(path, base..file_size).await?;
}
let reader = ParquetVectorReader::new(tail.clone(), "");
let ailk_offset = match reader.kv_metadata("ailake.footer_offset")? {
Some(v) => v.parse::<u64>().map_err(|_| AilakeError::NotAnAilakeFile)?,
None => return Err(AilakeError::NotAnAilakeFile),
};
Ok((ailk_offset, TailBuf { base, bytes: tail }))
}