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    pub(crate) fn counters(&self) -> (u32, u32) {
86        (self.table_id_counter, self.index_id_counter)
87    }
88
89    pub(crate) fn set_counters(&mut self, table_id_counter: u32, index_id_counter: u32) {
90        self.table_id_counter = table_id_counter;
91        self.index_id_counter = index_id_counter;
92    }
93
94    pub(crate) fn insert_table_unchecked(&mut self, table: TableMetadata) {
95        self.tables.insert(table.name.clone(), table);
96    }
97
98    pub(crate) fn remove_table_unchecked(&mut self, name: &str) {
99        self.tables.remove(name);
100        self.indexes.retain(|_, idx| idx.table != name);
101    }
102
103    pub(crate) fn insert_index_unchecked(&mut self, index: IndexMetadata) {
104        self.indexes.insert(index.name.clone(), index);
105    }
106
107    pub(crate) fn remove_index_unchecked(&mut self, name: &str) {
108        self.indexes.remove(name);
109    }
110}
111
112impl Catalog for MemoryCatalog {
113    fn create_table(&mut self, table: TableMetadata) -> Result<(), PlannerError> {
114        if self.tables.contains_key(&table.name) {
115            return Err(PlannerError::table_already_exists(&table.name));
116        }
117        self.tables.insert(table.name.clone(), table);
118        Ok(())
119    }
120
121    fn get_table(&self, name: &str) -> Option<&TableMetadata> {
122        self.tables.get(name)
123    }
124
125    fn drop_table(&mut self, name: &str) -> Result<(), PlannerError> {
126        if self.tables.remove(name).is_none() {
127            return Err(PlannerError::TableNotFound {
128                name: name.to_string(),
129                line: 0,
130                column: 0,
131            });
132        }
133        // Also drop all indexes for this table
134        self.indexes.retain(|_, idx| idx.table != name);
135        Ok(())
136    }
137
138    fn create_index(&mut self, index: IndexMetadata) -> Result<(), PlannerError> {
139        // Check for duplicate index name
140        if self.indexes.contains_key(&index.name) {
141            return Err(PlannerError::index_already_exists(&index.name));
142        }
143
144        // Verify target table exists
145        let table = self
146            .tables
147            .get(&index.table)
148            .ok_or_else(|| PlannerError::TableNotFound {
149                name: index.table.clone(),
150                line: 0,
151                column: 0,
152            })?;
153
154        // Verify all target columns exist in table
155        for column in &index.columns {
156            if table.get_column(column).is_none() {
157                return Err(PlannerError::ColumnNotFound {
158                    column: column.clone(),
159                    table: index.table.clone(),
160                    line: 0,
161                    col: 0,
162                });
163            }
164        }
165
166        self.indexes.insert(index.name.clone(), index);
167        Ok(())
168    }
169
170    fn get_index(&self, name: &str) -> Option<&IndexMetadata> {
171        self.indexes.get(name)
172    }
173
174    fn get_indexes_for_table(&self, table: &str) -> Vec<&IndexMetadata> {
175        self.indexes
176            .values()
177            .filter(|idx| idx.table == table)
178            .collect()
179    }
180
181    fn drop_index(&mut self, name: &str) -> Result<(), PlannerError> {
182        if self.indexes.remove(name).is_none() {
183            return Err(PlannerError::index_not_found(name));
184        }
185        Ok(())
186    }
187
188    fn table_exists(&self, name: &str) -> bool {
189        self.tables.contains_key(name)
190    }
191
192    fn index_exists(&self, name: &str) -> bool {
193        self.indexes.contains_key(name)
194    }
195
196    fn next_table_id(&mut self) -> u32 {
197        self.table_id_counter += 1;
198        self.table_id_counter
199    }
200
201    fn next_index_id(&mut self) -> u32 {
202        self.index_id_counter += 1;
203        self.index_id_counter
204    }
205}