use std::collections::HashMap;
use std::sync::Arc;
use linkrs_core::error::{DBError, QueryError};
use linkrs_core::metadata::MacroManager;
use linkrs_core::types::expr::{substitute_variables, Expression, FunctionArg};
use linkrs_core::{DBResult, DataType};
use super::bind::Binder;
use super::bound::BoundExpression;
pub const MAX_MACRO_EXPANSION_DEPTH: usize = 32;
impl Binder {
pub fn with_macro_manager(mut self, manager: Arc<MacroManager>) -> Self {
self.macro_manager = Some(manager);
self
}
pub(crate) fn try_expand_macro(
&mut self,
name: &str,
args: &[FunctionArg],
type_hint: Option<&DataType>,
) -> DBResult<Option<BoundExpression>> {
let Some(ref manager) = self.macro_manager else {
return Ok(None);
};
let Some(def) = manager.get_macro(name) else {
return Ok(None);
};
let canonical = name.to_ascii_uppercase();
if self.expanding.iter().any(|n| n == &canonical) {
return Err(DBError::from(QueryError::invalid_query(format!(
"Recursive macro expansion detected for macro '{}'",
def.name
))));
}
if self.expanding.len() >= MAX_MACRO_EXPANSION_DEPTH {
return Err(DBError::from(QueryError::invalid_query(format!(
"Macro expansion depth exceeds limit ({}) for macro '{}'",
MAX_MACRO_EXPANSION_DEPTH, def.name
))));
}
let mut mapping: HashMap<String, Expression> = HashMap::new();
let mut assigned = vec![false; def.params.len()];
let mut positional = 0usize;
for arg in args {
match arg {
FunctionArg::Positional(expr) => {
while positional < def.params.len() && assigned[positional] {
positional += 1;
}
if positional >= def.params.len() {
return Err(DBError::from(QueryError::invalid_query(format!(
"Macro '{}' takes at most {} arguments ({} given)",
def.name,
def.params.len(),
args.len()
))));
}
mapping.insert(def.params[positional].name.clone(), expr.clone());
assigned[positional] = true;
positional += 1;
}
FunctionArg::Named {
name: arg_name,
value,
} => {
let Some(index) = def
.params
.iter()
.position(|p| p.name.eq_ignore_ascii_case(arg_name))
else {
return Err(DBError::from(QueryError::invalid_query(format!(
"Macro '{}' has no parameter named '{}'",
def.name, arg_name
))));
};
if assigned[index] {
return Err(DBError::from(QueryError::invalid_query(format!(
"Duplicate argument for macro '{}' parameter '{}'",
def.name, def.params[index].name
))));
}
mapping.insert(def.params[index].name.clone(), value.clone());
assigned[index] = true;
}
}
}
for (index, param) in def.params.iter().enumerate() {
if !assigned[index] {
match ¶m.default {
Some(default) => {
mapping.insert(param.name.clone(), default.clone());
}
None => {
return Err(DBError::from(QueryError::invalid_query(format!(
"Macro '{}' missing required argument '{}'",
def.name, param.name
))));
}
}
}
}
let expanded = substitute_variables(&def.body, &mapping);
self.expanding.push(canonical);
let bound = self.bind_inner_expr(&expanded, type_hint);
self.expanding.pop();
bound.map(Some)
}
}