weavatrix-search-vector 0.3.1

Persistent, mutable, bounded vector candidate search for Rust and Weavatrix
Documentation
use super::encoder::write_snapshot_stream as encode_snapshot;
use crate::error::SearchError;
use crate::hnsw::VectorIndex;
use std::ffi::OsString;
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

pub(super) fn write_snapshot(index: &VectorIndex, target: &Path) -> Result<(), SearchError> {
    let temporary = temporary_path(target, "tmp")?;
    let result = (|| {
        let mut file = OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&temporary)
            .map_err(|error| SearchError::storage("create snapshot", &error))?;
        encode_snapshot(index, &mut file)?;
        file.sync_all()
            .map_err(|error| SearchError::storage("flush snapshot", &error))?;
        drop(file);
        replace_snapshot(&temporary, target)
    })();
    if result.is_err() {
        let _ = std::fs::remove_file(&temporary);
    }
    result
}

pub(super) use super::encoder::write_snapshot_stream;

fn temporary_path(target: &Path, label: &str) -> Result<PathBuf, SearchError> {
    let file_name = target
        .file_name()
        .ok_or(SearchError::InvalidConfig("snapshot path has no file name"))?;
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_or(0, |duration| duration.as_nanos());
    let mut temporary_name = OsString::from(file_name);
    temporary_name.push(format!(".{label}-{}-{nonce}", std::process::id()));
    Ok(target.with_file_name(temporary_name))
}

fn replace_snapshot(temporary: &Path, target: &Path) -> Result<(), SearchError> {
    match std::fs::rename(temporary, target) {
        Ok(()) => Ok(()),
        Err(_first_error) if target.exists() => {
            let backup = temporary_path(target, "backup")?;
            std::fs::rename(target, &backup)
                .map_err(|error| SearchError::storage("backup old snapshot", &error))?;
            match std::fs::rename(temporary, target) {
                Ok(()) => {
                    let _ = std::fs::remove_file(backup);
                    Ok(())
                }
                Err(error) => {
                    let _ = std::fs::rename(&backup, target);
                    Err(SearchError::storage("replace snapshot", &error))
                }
            }
        }
        Err(error) => Err(SearchError::storage("replace snapshot", &error)),
    }
}