Skip to main content

tensorlogic_adapters/database/
memory.rs

1//! In-memory database implementation for testing and development.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::{SchemaDatabase, SchemaId, SchemaMetadata, SchemaVersion};
8use crate::{AdapterError, SymbolTable};
9
10/// In-memory database implementation for testing and development.
11///
12/// This provides a simple in-memory store that implements the SchemaDatabase trait
13/// without requiring external database dependencies. Useful for:
14/// - Testing
15/// - Development
16/// - Small-scale applications
17/// - Temporary storage
18pub struct MemoryDatabase {
19    schemas: HashMap<SchemaId, StoredSchema>,
20    next_id: u64,
21    name_index: HashMap<String, Vec<SchemaId>>,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25struct StoredSchema {
26    id: SchemaId,
27    name: String,
28    version: u32,
29    table: SymbolTable,
30    created_at: u64,
31    updated_at: u64,
32    description: Option<String>,
33}
34
35impl MemoryDatabase {
36    /// Create a new empty memory database.
37    pub fn new() -> Self {
38        Self {
39            schemas: HashMap::new(),
40            next_id: 1,
41            name_index: HashMap::new(),
42        }
43    }
44
45    /// Get current timestamp (Unix epoch seconds).
46    fn current_timestamp() -> u64 {
47        std::time::SystemTime::now()
48            .duration_since(std::time::UNIX_EPOCH)
49            .expect("SystemTime after UNIX_EPOCH")
50            .as_secs()
51    }
52
53    /// Find latest version for a schema name.
54    fn find_latest_version(&self, name: &str) -> Option<SchemaId> {
55        self.name_index.get(name).and_then(|ids| {
56            ids.iter()
57                .filter_map(|id| self.schemas.get(id))
58                .max_by_key(|s| s.version)
59                .map(|s| s.id)
60        })
61    }
62}
63
64impl Default for MemoryDatabase {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl SchemaDatabase for MemoryDatabase {
71    fn store_schema(&mut self, name: &str, table: &SymbolTable) -> Result<SchemaId, AdapterError> {
72        let now = Self::current_timestamp();
73
74        // Check if schema with this name exists
75        let version = if let Some(existing_id) = self.find_latest_version(name) {
76            if let Some(existing) = self.schemas.get(&existing_id) {
77                existing.version + 1
78            } else {
79                1
80            }
81        } else {
82            1
83        };
84
85        let id = SchemaId(self.next_id);
86        self.next_id += 1;
87
88        let stored = StoredSchema {
89            id,
90            name: name.to_string(),
91            version,
92            table: table.clone(),
93            created_at: now,
94            updated_at: now,
95            description: None,
96        };
97
98        self.schemas.insert(id, stored);
99
100        // Update name index
101        self.name_index
102            .entry(name.to_string())
103            .or_default()
104            .push(id);
105
106        Ok(id)
107    }
108
109    fn load_schema(&self, id: SchemaId) -> Result<SymbolTable, AdapterError> {
110        self.schemas
111            .get(&id)
112            .map(|s| s.table.clone())
113            .ok_or_else(|| {
114                AdapterError::InvalidOperation(format!("Schema with ID {:?} not found", id))
115            })
116    }
117
118    fn load_schema_by_name(&self, name: &str) -> Result<SymbolTable, AdapterError> {
119        let id = self.find_latest_version(name).ok_or_else(|| {
120            AdapterError::InvalidOperation(format!("Schema '{}' not found", name))
121        })?;
122
123        self.load_schema(id)
124    }
125
126    fn list_schemas(&self) -> Result<Vec<SchemaMetadata>, AdapterError> {
127        let mut metadata: Vec<SchemaMetadata> = self
128            .schemas
129            .values()
130            .map(|s| SchemaMetadata {
131                id: s.id,
132                name: s.name.clone(),
133                version: s.version,
134                created_at: s.created_at,
135                updated_at: s.updated_at,
136                num_domains: s.table.domains.len(),
137                num_predicates: s.table.predicates.len(),
138                num_variables: s.table.variables.len(),
139                description: s.description.clone(),
140            })
141            .collect();
142
143        metadata.sort_by_key(|m| m.name.clone());
144        Ok(metadata)
145    }
146
147    fn delete_schema(&mut self, id: SchemaId) -> Result<(), AdapterError> {
148        if let Some(schema) = self.schemas.remove(&id) {
149            // Remove from name index
150            if let Some(ids) = self.name_index.get_mut(&schema.name) {
151                ids.retain(|&i| i != id);
152                if ids.is_empty() {
153                    self.name_index.remove(&schema.name);
154                }
155            }
156            Ok(())
157        } else {
158            Err(AdapterError::InvalidOperation(format!(
159                "Schema with ID {:?} not found",
160                id
161            )))
162        }
163    }
164
165    fn search_schemas(&self, pattern: &str) -> Result<Vec<SchemaMetadata>, AdapterError> {
166        let pattern_lower = pattern.to_lowercase();
167        let mut results: Vec<SchemaMetadata> = self
168            .schemas
169            .values()
170            .filter(|s| s.name.to_lowercase().contains(&pattern_lower))
171            .map(|s| SchemaMetadata {
172                id: s.id,
173                name: s.name.clone(),
174                version: s.version,
175                created_at: s.created_at,
176                updated_at: s.updated_at,
177                num_domains: s.table.domains.len(),
178                num_predicates: s.table.predicates.len(),
179                num_variables: s.table.variables.len(),
180                description: s.description.clone(),
181            })
182            .collect();
183
184        results.sort_by_key(|m| m.name.clone());
185        Ok(results)
186    }
187
188    fn get_schema_history(&self, name: &str) -> Result<Vec<SchemaVersion>, AdapterError> {
189        let ids = self.name_index.get(name).ok_or_else(|| {
190            AdapterError::InvalidOperation(format!("Schema '{}' not found", name))
191        })?;
192
193        let mut versions: Vec<SchemaVersion> = ids
194            .iter()
195            .filter_map(|id| {
196                self.schemas.get(id).map(|s| SchemaVersion {
197                    version: s.version,
198                    timestamp: s.created_at,
199                    description: format!("Version {}", s.version),
200                    schema_id: s.id,
201                })
202            })
203            .collect();
204
205        versions.sort_by_key(|v| v.version);
206        Ok(versions)
207    }
208}