siftdb-core 0.2.2

High-performance grep-native database for code and text collections with regex support
Documentation
pub mod storage;
pub mod types;
pub mod ingest;
pub mod index;
pub mod query;
pub mod bench;
pub mod fst_index;
pub mod incremental;
pub mod inverted_index;
pub mod tombstone;
pub mod locking;
pub mod compaction;

pub use types::*;
pub use bench::{SiftDBBenchmark, AdvancedBenchmark, AdvancedBenchmarkResults};
pub use compaction::{CollectionCompactor, CompactionManager, CompactionStatus};

use anyhow::Result;
use crate::locking::{SWMRLockManager, ReadLock, WriteLock};

/// SiftDB collection - the main entry point
pub struct SiftDB {
    path: std::path::PathBuf,
    lock_manager: SWMRLockManager,
}

impl SiftDB {
    /// Open an existing SiftDB collection
    pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
        let path = path.as_ref().to_path_buf();
        if !path.exists() {
            anyhow::bail!("SiftDB collection does not exist at: {}", path.display());
        }
        let lock_manager = SWMRLockManager::new(&path);
        Ok(Self { path, lock_manager })
    }

    /// Initialize a new SiftDB collection
    pub fn init<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
        let path = path.as_ref().to_path_buf();
        
        // Create directory structure
        std::fs::create_dir_all(&path)?;
        std::fs::create_dir_all(path.join("store"))?;
        std::fs::create_dir_all(path.join("index"))?;
        std::fs::create_dir_all(path.join("tmp"))?;
        std::fs::create_dir_all(path.join("gc"))?;

        // Create initial manifest
        let manifest = Manifest::new(0);
        manifest.write_to_file(&path.join("MANIFEST.a"))?;

        let lock_manager = SWMRLockManager::new(&path);
        Ok(Self { path, lock_manager })
    }

    /// Get a snapshot of the current state (with default timeout)
    pub fn snapshot(&self) -> Result<Snapshot> {
        self.snapshot_with_lock_config(30, "sift-cli".to_string())
    }

    /// Get a snapshot of the current state with custom lock configuration
    pub fn snapshot_with_lock_config(&self, timeout_secs: u64, holder_info: String) -> Result<Snapshot> {
        let manifest_path = self.path.join("MANIFEST.a");
        let manifest = if manifest_path.exists() {
            Manifest::read_from_file(&manifest_path)?
        } else {
            let manifest_path = self.path.join("MANIFEST.b");
            if manifest_path.exists() {
                Manifest::read_from_file(&manifest_path)?
            } else {
                anyhow::bail!("No valid manifest found");
            }
        };

        // Load indexes once when creating snapshot
        let path_index = crate::index::PathIndex::read_from_file(&self.path.join("index/path.json"))?;
        let handles_map = crate::index::HandlesMap::read_from_file(&self.path.join("index/handles.json"))?;
        
        // Load inverted index (if it exists, otherwise create empty one)
        let inverted_index = if self.path.join("index/terms.fst").exists() && self.path.join("index/posting_lists.json").exists() {
            crate::inverted_index::InvertedIndex::load_from_files(
                &self.path.join("index/terms.fst"),
                &self.path.join("index/posting_lists.json")
            )?
        } else {
            crate::inverted_index::InvertedIndex::new()
        };

        // For now, just create a placeholder for the lock - proper integration pending
        let _read_lock = self.lock_manager.acquire_read_lock(timeout_secs, holder_info)?;
        Ok(Snapshot {
            collection_path: self.path.clone(),
            epoch: manifest.epoch,
            path_index,
            handles_map,
            inverted_index,
            segment_cache: std::collections::HashMap::new(),
            _read_lock: None, // Simplified for now to avoid lifetime issues
        })
    }

    /// Perform incremental update of the collection
    pub fn incremental_update(
        &self,
        source_path: &std::path::Path,
        includes: &[String],
        excludes: &[String],
    ) -> Result<crate::incremental::DeltaManifest> {
        let updater = crate::incremental::IncrementalUpdater::new(&self.path);
        
        // Scan for changes
        let changes = updater.scan_for_changes(source_path, includes, excludes)?;
        
        if changes.is_empty() {
            anyhow::bail!("No changes detected since last import");
        }

        println!("Found {} changed files:", changes.len());
        for change in &changes {
            match change.change_type {
                crate::incremental::ChangeType::Added => {
                    println!("  + {}", change.path.display());
                }
                crate::incremental::ChangeType::Modified => {
                    println!("  M {}", change.path.display());
                }
                crate::incremental::ChangeType::Deleted => {
                    println!("  - {}", change.path.display());
                }
            }
        }

        // Apply changes
        let delta_manifest = updater.apply_changes(changes, source_path)?;
        
        Ok(delta_manifest)
    }

    /// Check if incremental update is available
    pub fn has_incremental_cache(&self) -> bool {
        let cache_path = self.path.join("index").join("file_cache.json");
        cache_path.exists()
    }
}

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}