alopex_sql/catalog/
memory.rs

1//! In-memory catalog implementation for the Alopex SQL dialect.
2//!
3//! This module provides [`MemoryCatalog`], a HashMap-based implementation
4//! of the [`Catalog`] trait for use in v0.1.1.
5
6use std::collections::HashMap;
7
8use super::{Catalog, IndexMetadata, TableMetadata};
9use crate::planner::PlannerError;
10
11/// In-memory catalog implementation using HashMaps.
12///
13/// This is the default catalog implementation for v0.1.2.
14/// It stores all metadata in memory and does not persist across restarts.
15///
16/// # Thread Safety
17///
18/// `MemoryCatalog` is not thread-safe. For concurrent access, wrap it in
19/// appropriate synchronization primitives (e.g., `Arc<RwLock<MemoryCatalog>>`).
20///
21/// # Example
22///
23/// ```
24/// use alopex_sql::catalog::{Catalog, MemoryCatalog, TableMetadata, ColumnMetadata};
25/// use alopex_sql::planner::types::ResolvedType;
26///
27/// let mut catalog = MemoryCatalog::new();
28///
29/// // Create a table
30/// let table = TableMetadata::new("users", vec![
31///     ColumnMetadata::new("id", ResolvedType::Integer),
32///     ColumnMetadata::new("name", ResolvedType::Text),
33/// ]);
34/// catalog.create_table(table).unwrap();
35///
36/// // Access the table
37/// assert!(catalog.table_exists("users"));
38/// let table = catalog.get_table("users").unwrap();
39/// assert_eq!(table.column_count(), 2);
40/// ```
41#[derive(Debug, Default)]
42pub struct MemoryCatalog {
43    /// Tables stored by name.
44    tables: HashMap<String, TableMetadata>,
45    /// Indexes stored by name.
46    indexes: HashMap<String, IndexMetadata>,
47    /// Counter for generating unique table IDs (starts at 0, first ID is 1).
48    table_id_counter: u32,
49    /// Counter for generating unique index IDs (starts at 0, first ID is 1).
50    index_id_counter: u32,
51}
52
53impl MemoryCatalog {
54    /// Create a new empty in-memory catalog.
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Get the number of tables in the catalog.
60    pub fn table_count(&self) -> usize {
61        self.tables.len()
62    }
63
64    /// Get the number of indexes in the catalog.
65    pub fn index_count(&self) -> usize {
66        self.indexes.len()
67    }
68
69    /// Get all table names in the catalog.
70    pub fn table_names(&self) -> Vec<&str> {
71        self.tables.keys().map(|s| s.as_str()).collect()
72    }
73
74    /// Get all index names in the catalog.
75    pub fn index_names(&self) -> Vec<&str> {
76        self.indexes.keys().map(|s| s.as_str()).collect()
77    }
78
79    /// Clear all tables and indexes from the catalog.
80    pub fn clear(&mut self) {
81        self.tables.clear();
82        self.indexes.clear();
83    }
84}
85
86impl Catalog for MemoryCatalog {
87    fn create_table(&mut self, table: TableMetadata) -> Result<(), PlannerError> {
88        if self.tables.contains_key(&table.name) {
89            return Err(PlannerError::table_already_exists(&table.name));
90        }
91        self.tables.insert(table.name.clone(), table);
92        Ok(())
93    }
94
95    fn get_table(&self, name: &str) -> Option<&TableMetadata> {
96        self.tables.get(name)
97    }
98
99    fn drop_table(&mut self, name: &str) -> Result<(), PlannerError> {
100        if self.tables.remove(name).is_none() {
101            return Err(PlannerError::TableNotFound {
102                name: name.to_string(),
103                line: 0,
104                column: 0,
105            });
106        }
107        // Also drop all indexes for this table
108        self.indexes.retain(|_, idx| idx.table != name);
109        Ok(())
110    }
111
112    fn create_index(&mut self, index: IndexMetadata) -> Result<(), PlannerError> {
113        // Check for duplicate index name
114        if self.indexes.contains_key(&index.name) {
115            return Err(PlannerError::index_already_exists(&index.name));
116        }
117
118        // Verify target table exists
119        let table = self
120            .tables
121            .get(&index.table)
122            .ok_or_else(|| PlannerError::TableNotFound {
123                name: index.table.clone(),
124                line: 0,
125                column: 0,
126            })?;
127
128        // Verify all target columns exist in table
129        for column in &index.columns {
130            if table.get_column(column).is_none() {
131                return Err(PlannerError::ColumnNotFound {
132                    column: column.clone(),
133                    table: index.table.clone(),
134                    line: 0,
135                    col: 0,
136                });
137            }
138        }
139
140        self.indexes.insert(index.name.clone(), index);
141        Ok(())
142    }
143
144    fn get_index(&self, name: &str) -> Option<&IndexMetadata> {
145        self.indexes.get(name)
146    }
147
148    fn get_indexes_for_table(&self, table: &str) -> Vec<&IndexMetadata> {
149        self.indexes
150            .values()
151            .filter(|idx| idx.table == table)
152            .collect()
153    }
154
155    fn drop_index(&mut self, name: &str) -> Result<(), PlannerError> {
156        if self.indexes.remove(name).is_none() {
157            return Err(PlannerError::index_not_found(name));
158        }
159        Ok(())
160    }
161
162    fn table_exists(&self, name: &str) -> bool {
163        self.tables.contains_key(name)
164    }
165
166    fn index_exists(&self, name: &str) -> bool {
167        self.indexes.contains_key(name)
168    }
169
170    fn next_table_id(&mut self) -> u32 {
171        self.table_id_counter += 1;
172        self.table_id_counter
173    }
174
175    fn next_index_id(&mut self) -> u32 {
176        self.index_id_counter += 1;
177        self.index_id_counter
178    }
179}