Skip to main content

linkrs_core/metadata/
macro_manager.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5use serde::{Deserialize, Serialize};
6
7use crate::types::expr::Expression;
8use crate::wal::redo::{CreateMacroRedo, DropMacroRedo};
9use crate::wal::traits::WalWriter;
10use crate::wal::types::WalOpType;
11use crate::StorageError;
12
13/// A single macro parameter with an optional default expression.
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct MacroParamDef {
16    /// Parameter name (case-insensitive at expansion time).
17    pub name: String,
18    /// Default value used when the call site omits this argument.
19    pub default: Option<Expression>,
20}
21
22/// A user-defined macro: a named, parameterized expression template.
23///
24/// Macros are pure query-time sugar: they are stored as parsed [`Expression`]
25/// trees and expanded by the binder before planning. The executor never sees
26/// a macro.
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct MacroDef {
29    /// Macro name (stored as written; looked up case-insensitively).
30    pub name: String,
31    /// Ordered parameter list.
32    pub params: Vec<MacroParamDef>,
33    /// Macro body template. Parameter references are `Expression::Variable`
34    /// nodes naming a parameter; expansion substitutes call-site arguments.
35    pub body: Expression,
36}
37
38impl MacroDef {
39    pub fn new(name: String, params: Vec<MacroParamDef>, body: Expression) -> Self {
40        Self { name, params, body }
41    }
42
43    /// Number of required parameters (those without defaults).
44    pub fn required_params(&self) -> usize {
45        self.params.iter().filter(|p| p.default.is_none()).count()
46    }
47}
48
49/// Trait for macro persistence backends.
50///
51/// The storage engine can implement this trait against its catalog KV area;
52/// until then [`MemMacroStorage`] provides the default in-memory backend.
53pub trait MacroStorage: Send + Sync {
54    /// Load all macro definitions from storage.
55    fn load_all(&self) -> Result<Vec<MacroDef>, StorageError>;
56    /// Save (insert or overwrite) a macro definition.
57    fn save(&self, def: &MacroDef) -> Result<(), StorageError>;
58    /// Delete a macro definition by name.
59    fn delete(&self, name: &str) -> Result<(), StorageError>;
60}
61
62/// In-memory [`MacroStorage`] backend (no durability beyond WAL).
63#[derive(Debug, Default)]
64pub struct MemMacroStorage {
65    data: RwLock<HashMap<String, MacroDef>>,
66}
67
68impl MemMacroStorage {
69    pub fn new() -> Self {
70        Self {
71            data: RwLock::new(HashMap::new()),
72        }
73    }
74}
75
76impl MacroStorage for MemMacroStorage {
77    fn load_all(&self) -> Result<Vec<MacroDef>, StorageError> {
78        Ok(self.data.read().values().cloned().collect())
79    }
80
81    fn save(&self, def: &MacroDef) -> Result<(), StorageError> {
82        self.data.write().insert(def.name.clone(), def.clone());
83        Ok(())
84    }
85
86    fn delete(&self, name: &str) -> Result<(), StorageError> {
87        self.data.write().remove(name);
88        Ok(())
89    }
90}
91
92/// Macro catalog manager.
93///
94/// Owns the in-memory macro map with optional persistence and WAL backing,
95/// mirroring [`super::sequence_manager::SequenceManager`].
96/// Thread-safe via `parking_lot::RwLock`.
97pub struct MacroManager {
98    macros: RwLock<HashMap<String, Arc<MacroDef>>>,
99    storage: Option<Arc<dyn MacroStorage>>,
100    wal_writer: Option<Arc<parking_lot::Mutex<dyn WalWriter>>>,
101}
102
103impl MacroManager {
104    /// Create a new MacroManager without persistence.
105    pub fn new() -> Self {
106        Self {
107            macros: RwLock::new(HashMap::new()),
108            storage: None,
109            wal_writer: None,
110        }
111    }
112
113    /// Create a new MacroManager with a persistence backend.
114    pub fn with_storage(storage: Arc<dyn MacroStorage>) -> Self {
115        Self {
116            macros: RwLock::new(HashMap::new()),
117            storage: Some(storage),
118            wal_writer: None,
119        }
120    }
121
122    /// Create a new MacroManager with persistence and WAL support.
123    pub fn with_wal(
124        storage: Arc<dyn MacroStorage>,
125        wal_writer: Arc<parking_lot::Mutex<dyn WalWriter>>,
126    ) -> Self {
127        Self {
128            macros: RwLock::new(HashMap::new()),
129            storage: Some(storage),
130            wal_writer: Some(wal_writer),
131        }
132    }
133
134    fn canonical(name: &str) -> String {
135        name.to_ascii_uppercase()
136    }
137
138    /// Initialize by loading all macros from storage.
139    pub fn initialize(&self) -> Result<(), StorageError> {
140        if let Some(ref storage) = self.storage {
141            let defs = storage.load_all()?;
142            let mut map = self.macros.write();
143            for def in defs {
144                map.insert(Self::canonical(&def.name), Arc::new(def));
145            }
146        }
147        Ok(())
148    }
149
150    /// Validate a macro definition structurally (no catalog access).
151    ///
152    /// Checks: non-empty name, no duplicate parameter names, and that the
153    /// body only references declared parameters or globals. Unknown
154    /// `Expression::Variable` leaves that are not parameters are treated as
155    /// query variables (allowed); direct self-reference by macro name in a
156    /// nested call is rejected at expansion time.
157    pub fn validate_definition(def: &MacroDef) -> Result<(), StorageError> {
158        if def.name.trim().is_empty() {
159            return Err(StorageError::db_error("Macro name must not be empty"));
160        }
161        let mut seen = std::collections::HashSet::new();
162        for param in &def.params {
163            if param.name.trim().is_empty() {
164                return Err(StorageError::db_error(format!(
165                    "Macro '{}' has an empty parameter name",
166                    def.name
167                )));
168            }
169            let key = param.name.to_ascii_uppercase();
170            if !seen.insert(key) {
171                return Err(StorageError::db_error(format!(
172                    "Macro '{}' has duplicate parameter '{}'",
173                    def.name, param.name
174                )));
175            }
176        }
177        Ok(())
178    }
179
180    /// Create a new macro definition.
181    pub fn create_macro(&self, def: MacroDef) -> Result<(), StorageError> {
182        Self::validate_definition(&def)?;
183        let key = Self::canonical(&def.name);
184        {
185            let map = self.macros.read();
186            if map.contains_key(&key) {
187                return Err(StorageError::db_error(format!(
188                    "Macro '{}' already exists",
189                    def.name
190                )));
191            }
192        }
193
194        if let Some(ref storage) = self.storage {
195            storage.save(&def)?;
196        }
197        self.write_wal_create(&def)?;
198
199        let mut map = self.macros.write();
200        // Winner-takes-all under concurrency: second writer reports exists.
201        if map.contains_key(&key) {
202            return Err(StorageError::db_error(format!(
203                "Macro '{}' already exists",
204                def.name
205            )));
206        }
207        map.insert(key, Arc::new(def));
208        Ok(())
209    }
210
211    /// Drop (delete) a macro definition.
212    pub fn drop_macro(&self, name: &str) -> Result<(), StorageError> {
213        let key = Self::canonical(name);
214        {
215            let mut map = self.macros.write();
216            if map.remove(&key).is_none() {
217                return Err(StorageError::db_error(format!(
218                    "Macro '{}' does not exist",
219                    name
220                )));
221            }
222        }
223
224        if let Some(ref storage) = self.storage {
225            storage.delete(name)?;
226        }
227        self.write_wal_drop(name)?;
228        Ok(())
229    }
230
231    /// Look up a macro definition by name (case-insensitive).
232    pub fn get_macro(&self, name: &str) -> Option<Arc<MacroDef>> {
233        let map = self.macros.read();
234        map.get(&Self::canonical(name)).cloned()
235    }
236
237    /// Check if a macro exists (case-insensitive).
238    pub fn exists(&self, name: &str) -> bool {
239        let map = self.macros.read();
240        map.contains_key(&Self::canonical(name))
241    }
242
243    /// List all macro definitions, ordered by name for stable output.
244    pub fn list_macros(&self) -> Vec<Arc<MacroDef>> {
245        let map = self.macros.read();
246        let mut defs: Vec<Arc<MacroDef>> = map.values().cloned().collect();
247        defs.sort_by(|a, b| a.name.cmp(&b.name));
248        defs
249    }
250
251    fn wal_timestamp() -> u64 {
252        std::time::SystemTime::now()
253            .duration_since(std::time::UNIX_EPOCH)
254            .unwrap_or_default()
255            .as_millis() as u64
256    }
257
258    fn write_wal_create(&self, def: &MacroDef) -> Result<(), StorageError> {
259        if let Some(ref wal_writer) = self.wal_writer {
260            let redo = CreateMacroRedo {
261                space_name: String::new(),
262                name: def.name.clone(),
263                params: def.params.clone(),
264                body: def.body.clone(),
265            };
266            let payload = postcard::to_allocvec(&redo)
267                .map_err(|e| StorageError::serialize_error(e.to_string()))?;
268            let mut writer = wal_writer.lock();
269            writer
270                .append_entry(WalOpType::CreateMacro, Self::wal_timestamp(), &payload)
271                .map_err(|e| StorageError::db_error(format!("WAL write error: {}", e)))?;
272        }
273        Ok(())
274    }
275
276    fn write_wal_drop(&self, name: &str) -> Result<(), StorageError> {
277        if let Some(ref wal_writer) = self.wal_writer {
278            let redo = DropMacroRedo {
279                space_name: String::new(),
280                name: name.to_string(),
281            };
282            let payload = postcard::to_allocvec(&redo)
283                .map_err(|e| StorageError::serialize_error(e.to_string()))?;
284            let mut writer = wal_writer.lock();
285            writer
286                .append_entry(WalOpType::DropMacro, Self::wal_timestamp(), &payload)
287                .map_err(|e| StorageError::db_error(format!("WAL write error: {}", e)))?;
288        }
289        Ok(())
290    }
291
292    /// Replay a WAL `CreateMacro` record during recovery.
293    pub fn replay_create(&self, redo: &CreateMacroRedo) -> Result<(), StorageError> {
294        let def = MacroDef::new(redo.name.clone(), redo.params.clone(), redo.body.clone());
295        Self::validate_definition(&def)?;
296        let mut map = self.macros.write();
297        map.insert(Self::canonical(&def.name), Arc::new(def));
298        Ok(())
299    }
300
301    /// Replay a WAL `DropMacro` record during recovery.
302    pub fn replay_drop(&self, redo: &DropMacroRedo) -> Result<(), StorageError> {
303        let mut map = self.macros.write();
304        map.remove(&Self::canonical(&redo.name));
305        Ok(())
306    }
307}
308
309impl Default for MacroManager {
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315impl std::fmt::Debug for MacroManager {
316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317        f.debug_struct("MacroManager")
318            .field("count", &self.macros.read().len())
319            .finish()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use crate::types::expr::Expression;
327
328    fn sample_def(name: &str) -> MacroDef {
329        MacroDef::new(
330            name.to_string(),
331            vec![MacroParamDef {
332                name: "x".to_string(),
333                default: None,
334            }],
335            Expression::Binary {
336                left: Box::new(Expression::variable("x")),
337                op: crate::types::operators::BinaryOperator::Multiply,
338                right: Box::new(Expression::literal(crate::Value::Int(2))),
339            },
340        )
341    }
342
343    #[test]
344    fn test_macro_create_get_drop() {
345        let manager = MacroManager::new();
346        manager.create_macro(sample_def("double")).unwrap();
347        assert!(manager.exists("double"));
348        assert!(manager.exists("DOUBLE"));
349        let def = manager.get_macro("Double").unwrap();
350        assert_eq!(def.params.len(), 1);
351        assert_eq!(def.required_params(), 1);
352        manager.drop_macro("double").unwrap();
353        assert!(!manager.exists("double"));
354    }
355
356    #[test]
357    fn test_macro_duplicate_create() {
358        let manager = MacroManager::new();
359        manager.create_macro(sample_def("m")).unwrap();
360        assert!(manager.create_macro(sample_def("M")).is_err());
361    }
362
363    #[test]
364    fn test_macro_drop_missing() {
365        let manager = MacroManager::new();
366        assert!(manager.drop_macro("nope").is_err());
367    }
368
369    #[test]
370    fn test_macro_duplicate_params_rejected() {
371        let mut def = sample_def("bad");
372        def.params.push(MacroParamDef {
373            name: "X".to_string(),
374            default: None,
375        });
376        assert!(MacroManager::validate_definition(&def).is_err());
377    }
378
379    #[test]
380    fn test_macro_list_sorted() {
381        let manager = MacroManager::new();
382        manager.create_macro(sample_def("b")).unwrap();
383        manager.create_macro(sample_def("a")).unwrap();
384        let names: Vec<String> = manager
385            .list_macros()
386            .iter()
387            .map(|d| d.name.clone())
388            .collect();
389        assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
390    }
391
392    #[test]
393    fn test_macro_with_storage() {
394        let storage = Arc::new(MemMacroStorage::new());
395        let manager = MacroManager::with_storage(storage);
396        manager.create_macro(sample_def("m1")).unwrap();
397        assert!(manager.exists("m1"));
398        manager.initialize().unwrap();
399        assert!(manager.exists("m1"));
400    }
401
402    #[test]
403    fn test_macro_replay() {
404        let manager = MacroManager::new();
405        let def = sample_def("r");
406        manager
407            .replay_create(&CreateMacroRedo {
408                space_name: String::new(),
409                name: def.name.clone(),
410                params: def.params.clone(),
411                body: def.body.clone(),
412            })
413            .unwrap();
414        assert!(manager.exists("r"));
415        manager
416            .replay_drop(&DropMacroRedo {
417                space_name: String::new(),
418                name: "r".to_string(),
419            })
420            .unwrap();
421        assert!(!manager.exists("r"));
422    }
423}