geographdb-core 0.3.1

Geometric graph database core - 3D spatial indexing for code analysis
Documentation
//! Symbol metadata section adapter for sectioned storage
//!
//! Handles reading/writing symbol metadata from/to a SYMBOL_METADATA section.

use anyhow::{Context, Result};

use super::sectioned::SectionedStorage;
use super::symbol_metadata::SymbolMetadataStore;

#[cfg(test)]
use super::symbol_metadata::SymbolMetadata;

/// Symbol metadata section adapter
///
/// Handles reading/writing symbol metadata from/to a SYMBOL_METADATA section.
pub struct SymbolMetadataSectionAdapter;

impl SymbolMetadataSectionAdapter {
    pub const SECTION_NAME: &'static str = "SYMBOL_METADATA";

    /// Load symbol metadata from the section
    pub fn load(storage: &mut SectionedStorage) -> Result<SymbolMetadataStore> {
        let bytes = storage
            .read_section(Self::SECTION_NAME)
            .context("SYMBOL_METADATA section not found or empty")?;
        SymbolMetadataStore::from_bytes(&bytes).context("Failed to parse SYMBOL_METADATA section")
    }

    /// Save symbol metadata to the section
    ///
    /// Handles auto-resizing if capacity is exceeded.
    pub fn save(storage: &mut SectionedStorage, store: &SymbolMetadataStore) -> Result<()> {
        let bytes = store.to_bytes();
        let required = bytes.len() as u64;

        if storage.get_section(Self::SECTION_NAME).is_some() {
            let result = storage.write_section(Self::SECTION_NAME, &bytes);

            if let Err(e) = result {
                // Check if it's a capacity error
                if e.to_string().contains("overflow") || e.to_string().contains("capacity") {
                    // Need to resize - use 2x current capacity or enough for data, whichever is larger
                    let current = storage.get_section(Self::SECTION_NAME).unwrap();
                    let new_capacity = (current.capacity * 2).max(required * 2);
                    storage
                        .resize_section(Self::SECTION_NAME, new_capacity)
                        .context("Failed to resize SYMBOL_METADATA section")?;
                    // Retry write
                    storage.write_section(Self::SECTION_NAME, &bytes)?;
                } else {
                    return Err(e);
                }
            }
        } else {
            // Create new section with reasonable initial capacity (1MB)
            let section_capacity = (1024 * 1024).max(required * 2);
            storage.create_section(Self::SECTION_NAME, section_capacity, 0)?;
            storage.write_section(Self::SECTION_NAME, &bytes)?;
        }

        storage.flush()?;
        Ok(())
    }

    /// Initialize an empty SYMBOL_METADATA section with reasonable default capacity
    pub fn init(storage: &mut SectionedStorage) -> Result<()> {
        // Create section with default capacity (1MB) for future growth
        let default_capacity = 1024 * 1024; // 1MB
        storage.create_section(Self::SECTION_NAME, default_capacity, 0)?;

        // Write empty data
        let empty = SymbolMetadataStore::new();
        let bytes = empty.to_bytes();
        storage.write_section(Self::SECTION_NAME, &bytes)?;
        storage.flush()?;

        Ok(())
    }

    /// Check if SYMBOL_METADATA section exists
    pub fn exists(storage: &SectionedStorage) -> bool {
        storage.get_section(Self::SECTION_NAME).is_some()
    }
}

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

    #[test]
    fn test_symbol_metadata_section_roundtrip() {
        let temp_dir = tempdir().unwrap();
        let db_path = temp_dir.path().join("test_symbols.geo");

        // Create storage and init section
        let mut storage = SectionedStorage::create(&db_path).unwrap();
        SymbolMetadataSectionAdapter::init(&mut storage).unwrap();

        // Create symbol metadata store
        let mut store = SymbolMetadataStore::new();
        store.add(SymbolMetadata {
            symbol_id: 1,
            name: "my_func".to_string(),
            fqn: "crate::my_func".to_string(),
            file_path: "/src/lib.rs".to_string(),
            kind: 1,
            language: 1,
            byte_start: 100,
            byte_end: 200,
            start_line: 10,
            start_col: 0,
            end_line: 20,
            end_col: 1,
        });

        // Save
        SymbolMetadataSectionAdapter::save(&mut storage, &store).unwrap();
        drop(storage);

        // Reopen and load
        let mut storage = SectionedStorage::open(&db_path).unwrap();
        let restored = SymbolMetadataSectionAdapter::load(&mut storage).unwrap();

        // Verify
        assert_eq!(restored.symbol_count(), 1);
        let meta = restored.get(1).unwrap();
        assert_eq!(meta.name, "my_func");
        assert_eq!(meta.fqn, "crate::my_func");
        assert_eq!(meta.file_path, "/src/lib.rs");
    }

    #[test]
    fn test_symbol_metadata_section_exists() {
        let temp_dir = tempdir().unwrap();
        let db_path = temp_dir.path().join("test_exists.geo");

        // Create storage without section
        let mut storage = SectionedStorage::create(&db_path).unwrap();
        assert!(!SymbolMetadataSectionAdapter::exists(&storage));

        // Init section
        SymbolMetadataSectionAdapter::init(&mut storage).unwrap();
        assert!(SymbolMetadataSectionAdapter::exists(&storage));
    }
}