Skip to main content

geographdb_core/storage/
sectioned_symbol.rs

1//! Symbol metadata section adapter for sectioned storage
2//!
3//! Handles reading/writing symbol metadata from/to a SYMBOL_METADATA section.
4
5use anyhow::{Context, Result};
6
7use super::sectioned::SectionedStorage;
8use super::symbol_metadata::SymbolMetadataStore;
9
10#[cfg(test)]
11use super::symbol_metadata::SymbolMetadata;
12
13/// Symbol metadata section adapter
14///
15/// Handles reading/writing symbol metadata from/to a SYMBOL_METADATA section.
16pub struct SymbolMetadataSectionAdapter;
17
18impl SymbolMetadataSectionAdapter {
19    pub const SECTION_NAME: &'static str = "SYMBOL_METADATA";
20
21    /// Load symbol metadata from the section
22    pub fn load(storage: &mut SectionedStorage) -> Result<SymbolMetadataStore> {
23        let bytes = storage
24            .read_section(Self::SECTION_NAME)
25            .context("SYMBOL_METADATA section not found or empty")?;
26        SymbolMetadataStore::from_bytes(&bytes).context("Failed to parse SYMBOL_METADATA section")
27    }
28
29    /// Save symbol metadata to the section
30    ///
31    /// Handles auto-resizing if capacity is exceeded.
32    pub fn save(storage: &mut SectionedStorage, store: &SymbolMetadataStore) -> Result<()> {
33        let bytes = store.to_bytes();
34        let required = bytes.len() as u64;
35
36        if storage.get_section(Self::SECTION_NAME).is_some() {
37            let result = storage.write_section(Self::SECTION_NAME, &bytes);
38
39            if let Err(e) = result {
40                // Check if it's a capacity error
41                if e.to_string().contains("overflow") || e.to_string().contains("capacity") {
42                    // Need to resize - use 2x current capacity or enough for data, whichever is larger
43                    let current = storage.get_section(Self::SECTION_NAME).unwrap();
44                    let new_capacity = (current.capacity * 2).max(required * 2);
45                    storage
46                        .resize_section(Self::SECTION_NAME, new_capacity)
47                        .context("Failed to resize SYMBOL_METADATA section")?;
48                    // Retry write
49                    storage.write_section(Self::SECTION_NAME, &bytes)?;
50                } else {
51                    return Err(e);
52                }
53            }
54        } else {
55            // Create new section with reasonable initial capacity (1MB)
56            let section_capacity = (1024 * 1024).max(required * 2);
57            storage.create_section(Self::SECTION_NAME, section_capacity, 0)?;
58            storage.write_section(Self::SECTION_NAME, &bytes)?;
59        }
60
61        storage.flush()?;
62        Ok(())
63    }
64
65    /// Initialize an empty SYMBOL_METADATA section with reasonable default capacity
66    pub fn init(storage: &mut SectionedStorage) -> Result<()> {
67        // Create section with default capacity (1MB) for future growth
68        let default_capacity = 1024 * 1024; // 1MB
69        storage.create_section(Self::SECTION_NAME, default_capacity, 0)?;
70
71        // Write empty data
72        let empty = SymbolMetadataStore::new();
73        let bytes = empty.to_bytes();
74        storage.write_section(Self::SECTION_NAME, &bytes)?;
75        storage.flush()?;
76
77        Ok(())
78    }
79
80    /// Check if SYMBOL_METADATA section exists
81    pub fn exists(storage: &SectionedStorage) -> bool {
82        storage.get_section(Self::SECTION_NAME).is_some()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use tempfile::tempdir;
90
91    #[test]
92    fn test_symbol_metadata_section_roundtrip() {
93        let temp_dir = tempdir().unwrap();
94        let db_path = temp_dir.path().join("test_symbols.geo");
95
96        // Create storage and init section
97        let mut storage = SectionedStorage::create(&db_path).unwrap();
98        SymbolMetadataSectionAdapter::init(&mut storage).unwrap();
99
100        // Create symbol metadata store
101        let mut store = SymbolMetadataStore::new();
102        store.add(SymbolMetadata {
103            symbol_id: 1,
104            name: "my_func".to_string(),
105            fqn: "crate::my_func".to_string(),
106            file_path: "/src/lib.rs".to_string(),
107            kind: 1,
108            language: 1,
109            byte_start: 100,
110            byte_end: 200,
111            start_line: 10,
112            start_col: 0,
113            end_line: 20,
114            end_col: 1,
115        });
116
117        // Save
118        SymbolMetadataSectionAdapter::save(&mut storage, &store).unwrap();
119        drop(storage);
120
121        // Reopen and load
122        let mut storage = SectionedStorage::open(&db_path).unwrap();
123        let restored = SymbolMetadataSectionAdapter::load(&mut storage).unwrap();
124
125        // Verify
126        assert_eq!(restored.symbol_count(), 1);
127        let meta = restored.get(1).unwrap();
128        assert_eq!(meta.name, "my_func");
129        assert_eq!(meta.fqn, "crate::my_func");
130        assert_eq!(meta.file_path, "/src/lib.rs");
131    }
132
133    #[test]
134    fn test_symbol_metadata_section_exists() {
135        let temp_dir = tempdir().unwrap();
136        let db_path = temp_dir.path().join("test_exists.geo");
137
138        // Create storage without section
139        let mut storage = SectionedStorage::create(&db_path).unwrap();
140        assert!(!SymbolMetadataSectionAdapter::exists(&storage));
141
142        // Init section
143        SymbolMetadataSectionAdapter::init(&mut storage).unwrap();
144        assert!(SymbolMetadataSectionAdapter::exists(&storage));
145    }
146}