linkrs_query/binder/
macro_expansion.rs1use std::collections::HashMap;
10use std::sync::Arc;
11
12use linkrs_core::error::{DBError, QueryError};
13use linkrs_core::metadata::MacroManager;
14use linkrs_core::types::expr::{substitute_variables, Expression, FunctionArg};
15use linkrs_core::{DBResult, DataType};
16
17use super::bind::Binder;
18use super::bound::BoundExpression;
19
20pub const MAX_MACRO_EXPANSION_DEPTH: usize = 32;
22
23impl Binder {
24 pub fn with_macro_manager(mut self, manager: Arc<MacroManager>) -> Self {
27 self.macro_manager = Some(manager);
28 self
29 }
30
31 pub(crate) fn try_expand_macro(
36 &mut self,
37 name: &str,
38 args: &[FunctionArg],
39 type_hint: Option<&DataType>,
40 ) -> DBResult<Option<BoundExpression>> {
41 let Some(ref manager) = self.macro_manager else {
42 return Ok(None);
43 };
44 let Some(def) = manager.get_macro(name) else {
45 return Ok(None);
46 };
47
48 let canonical = name.to_ascii_uppercase();
49 if self.expanding.iter().any(|n| n == &canonical) {
50 return Err(DBError::from(QueryError::invalid_query(format!(
51 "Recursive macro expansion detected for macro '{}'",
52 def.name
53 ))));
54 }
55 if self.expanding.len() >= MAX_MACRO_EXPANSION_DEPTH {
56 return Err(DBError::from(QueryError::invalid_query(format!(
57 "Macro expansion depth exceeds limit ({}) for macro '{}'",
58 MAX_MACRO_EXPANSION_DEPTH, def.name
59 ))));
60 }
61
62 let mut mapping: HashMap<String, Expression> = HashMap::new();
65 let mut assigned = vec![false; def.params.len()];
66 let mut positional = 0usize;
67 for arg in args {
68 match arg {
69 FunctionArg::Positional(expr) => {
70 while positional < def.params.len() && assigned[positional] {
71 positional += 1;
72 }
73 if positional >= def.params.len() {
74 return Err(DBError::from(QueryError::invalid_query(format!(
75 "Macro '{}' takes at most {} arguments ({} given)",
76 def.name,
77 def.params.len(),
78 args.len()
79 ))));
80 }
81 mapping.insert(def.params[positional].name.clone(), expr.clone());
82 assigned[positional] = true;
83 positional += 1;
84 }
85 FunctionArg::Named {
86 name: arg_name,
87 value,
88 } => {
89 let Some(index) = def
90 .params
91 .iter()
92 .position(|p| p.name.eq_ignore_ascii_case(arg_name))
93 else {
94 return Err(DBError::from(QueryError::invalid_query(format!(
95 "Macro '{}' has no parameter named '{}'",
96 def.name, arg_name
97 ))));
98 };
99 if assigned[index] {
100 return Err(DBError::from(QueryError::invalid_query(format!(
101 "Duplicate argument for macro '{}' parameter '{}'",
102 def.name, def.params[index].name
103 ))));
104 }
105 mapping.insert(def.params[index].name.clone(), value.clone());
106 assigned[index] = true;
107 }
108 }
109 }
110 for (index, param) in def.params.iter().enumerate() {
111 if !assigned[index] {
112 match ¶m.default {
113 Some(default) => {
114 mapping.insert(param.name.clone(), default.clone());
115 }
116 None => {
117 return Err(DBError::from(QueryError::invalid_query(format!(
118 "Macro '{}' missing required argument '{}'",
119 def.name, param.name
120 ))));
121 }
122 }
123 }
124 }
125
126 let expanded = substitute_variables(&def.body, &mapping);
127 self.expanding.push(canonical);
128 let bound = self.bind_inner_expr(&expanded, type_hint);
129 self.expanding.pop();
130 bound.map(Some)
131 }
132}