use rudb_common::{Error, LogicalType, Result};
use rudb_parse::Ast;
use rudb_parse::ast::{self, BinaryOp};
use rudb_plan::{ColumnBinding, Expr, ExprRef};
use crate::binder::Binder;
use crate::scope::Scope;
const LAMBDA_FUNCTIONS: &[(&str, &str)] = &[
("list_transform", TRANSFORM),
("list_apply", TRANSFORM),
("apply", TRANSFORM),
("array_transform", TRANSFORM),
("array_apply", TRANSFORM),
("list_filter", FILTER),
("filter", FILTER),
("array_filter", FILTER),
("list_reduce", REDUCE),
("array_reduce", REDUCE),
("reduce", REDUCE),
("invoke", INVOKE),
];
pub(crate) const TRANSFORM: &str = "list_transform";
pub(crate) const FILTER: &str = "list_filter";
pub(crate) const REDUCE: &str = "list_reduce";
pub(crate) const INVOKE: &str = "invoke";
pub(crate) fn lambda_function(written: &str) -> Option<&'static str> {
LAMBDA_FUNCTIONS
.iter()
.find(|(name, _)| rudb_catalog::same_name(written, name))
.map(|&(_, recorded)| recorded)
}
#[derive(Debug, Clone)]
pub(crate) struct Frame {
table: u32,
names: Vec<String>,
types: Vec<LogicalType>,
}
fn arrow_lambda(ast: &Ast, arg: ast::ExprRef) -> bool {
let bare = |expr: ast::ExprRef| matches!(ast.expr(expr), ast::Expr::Column { name } if ast.name(name).count() == 1);
match ast.expr(arg) {
ast::Expr::Binary { op: BinaryOp::Arrow, left, .. } => match ast.expr(left) {
ast::Expr::Row { items } => ast.expr_list(items).iter().all(|&item| bare(item)),
_ => bare(left),
},
_ => false,
}
}
fn any_lambda(ast: &Ast, arg: ast::ExprRef) -> bool {
matches!(ast.expr(arg), ast::Expr::Lambda { .. }) || arrow_lambda(ast, arg)
}
impl Binder<'_> {
pub(crate) fn in_lambda(&self) -> bool {
!self.lambda_frames.is_empty()
}
pub(crate) fn lambda_parameter(&mut self, word: &str) -> Option<ExprRef> {
let (binding, ty) = self.lambda_frames.iter().rev().find_map(|frame| {
let at = frame.names.iter().position(|name| rudb_catalog::same_name(name, word))?;
Some((ColumnBinding::new(frame.table, at as u32), frame.types[at].clone()))
})?;
Some(self.add_expr(Expr::LambdaParam(binding), ty))
}
pub(crate) fn bind_lambda_call(
&mut self,
ast: &Ast,
recorded: &'static str,
arguments: &[ast::ExprRef],
scope: &Scope,
) -> Result<ExprRef> {
if recorded == INVOKE {
return self.bind_invoke(ast, arguments, scope);
}
let reduce = recorded == REDUCE;
let (list, lambda, initial) = match *arguments {
[list, lambda] => (list, lambda, None),
[list, lambda, initial] if reduce => (list, lambda, Some(initial)),
_ => return Err(self.no_lambda_match(ast, recorded, arguments, scope)),
};
if any_lambda(ast, list)
|| !any_lambda(ast, lambda)
|| initial.is_some_and(|initial| any_lambda(ast, initial))
{
return Err(self.no_lambda_match(ast, recorded, arguments, scope));
}
let (names, body) = lambda_parts(ast, lambda)?;
if names.len() > 3 || (names.len() > 2 && !reduce) {
return Err(Error::binder(format!(
"This lambda function only supports up to {} lambda parameters!",
if reduce { "three" } else { "two" }
)));
}
let mut list = self.bind_expr(ast, list, scope)?;
let element = match self.plan().expr_type(list).clone() {
LogicalType::List(element) => *element,
LogicalType::Array(element, _) => {
let as_list = LogicalType::List(element.clone());
list = self.cast_to(list, &as_list);
*element
}
LogicalType::Null => return Ok(self.add_constant(rudb_common::Value::Null)),
_ => {
return Err(Error::binder("Invalid LIST argument during lambda function binding!"));
}
};
let list_type = self.plan().expr_type(list).clone();
let initial = match initial {
Some(initial) => Some(self.bind_expr(ast, initial, scope)?),
None => None,
};
let table = self.fresh_index();
let interned: Vec<_> = names.iter().map(|name| self.plan_mut().intern(name)).collect();
let params = self.plan_mut().add_name_list(&interned);
let (body, returns, initial) = if reduce {
self.bind_reduce(ast, body, scope, table, &names, element, initial)?
} else {
let types = [element, LogicalType::BigInt][..names.len()].to_vec();
let mut body = self.bind_lambda_body(ast, body, scope, table, &names, types)?;
if recorded == FILTER && self.plan().expr_type(body) != &LogicalType::Boolean {
body = self.checked_cast_to(body, &LogicalType::Boolean, false)?;
}
let returns = if recorded == FILTER {
list_type
} else {
LogicalType::List(Box::new(self.plan().expr_type(body).clone()))
};
(body, returns, None)
};
let body_type = self.plan().expr_type(body).clone();
let lambda = self.add_expr(Expr::Lambda { table, params, body }, body_type);
let args = match initial {
Some(initial) => self.plan_mut().add_expr_list(&[list, lambda, initial]),
None => self.plan_mut().add_expr_list(&[list, lambda]),
};
let name = self.plan_mut().intern(recorded);
Ok(self.add_expr(Expr::Function { name, args }, returns))
}
fn bind_invoke(
&mut self,
ast: &Ast,
arguments: &[ast::ExprRef],
scope: &Scope,
) -> Result<ExprRef> {
let Some((&lambda, rest)) = arguments.split_first() else {
return Err(self.no_lambda_match(ast, INVOKE, arguments, scope));
};
if !any_lambda(ast, lambda) {
if rest.iter().any(|&arg| any_lambda(ast, arg)) {
return Err(Error::binder("This scalar function requires a lambda expression!"));
}
if !rest.is_empty() {
let first = self.bind_expr(ast, lambda, scope)?;
if self.plan().expr_type(first) == &LogicalType::Null {
return Err(Error::binder(
"Invalid lambda expression passed to 'invoke' function.",
));
}
}
return Err(self.no_lambda_match(ast, INVOKE, arguments, scope));
}
let (names, body) = lambda_parts(ast, lambda)?;
let mut args = Vec::with_capacity(rest.len());
for &arg in rest {
args.push(self.bind_expr(ast, arg, scope)?);
}
if names.len() != args.len() {
let expected = if names.len() > args.len() {
format!("at least {}", args.len() + 1)
} else {
names.len().to_string()
};
return Err(Error::binder(format!(
"The number of lambda parameters does not match the number of arguments passed to \
the 'invoke' function, expected {expected}, got {}.",
args.len()
)));
}
let types: Vec<LogicalType> =
args.iter().map(|&arg| self.plan().expr_type(arg).clone()).collect();
let table = self.fresh_index();
let interned: Vec<_> = names.iter().map(|name| self.plan_mut().intern(name)).collect();
let params = self.plan_mut().add_name_list(&interned);
let body = self.bind_lambda_body(ast, body, scope, table, &names, types)?;
let returns = self.plan().expr_type(body).clone();
let lambda = self.add_expr(Expr::Lambda { table, params, body }, returns.clone());
args.insert(0, lambda);
let args = self.plan_mut().add_expr_list(&args);
let name = self.plan_mut().intern(INVOKE);
Ok(self.add_expr(Expr::Function { name, args }, returns))
}
fn bind_lambda_body(
&mut self,
ast: &Ast,
body: ast::ExprRef,
scope: &Scope,
table: u32,
names: &[String],
types: Vec<LogicalType>,
) -> Result<ExprRef> {
self.lambda_frames.push(Frame { table, names: names.to_vec(), types });
let body = self.bind_expr(ast, body, scope);
self.lambda_frames.pop();
body
}
#[allow(clippy::too_many_arguments)]
fn bind_reduce(
&mut self,
ast: &Ast,
body: ast::ExprRef,
scope: &Scope,
table: u32,
names: &[String],
element: LogicalType,
initial: Option<ExprRef>,
) -> Result<(ExprRef, LogicalType, Option<ExprRef>)> {
let count = names.len();
let typed = |accumulator: &LogicalType| {
[accumulator.clone(), element.clone(), LogicalType::BigInt][..count].to_vec()
};
let initial_type = initial.map(|initial| self.plan().expr_type(initial).clone());
let start = initial_type.clone().unwrap_or_else(|| element.clone());
let first = self.bind_lambda_body(ast, body, scope, table, names, typed(&start))?;
let returned = self.plan().expr_type(first).clone();
let widened = common_type(&start, &returned)
.ok_or_else(|| no_common_type(&start, &returned, initial.is_some()))?;
let (mut body, accumulator) = if initial.is_some() {
let mut body =
self.bind_lambda_body(ast, body, scope, table, names, typed(&widened))?;
let returned = self.plan().expr_type(body).clone();
if (has_decimal(&widened) || has_decimal(&returned)) && returned != widened {
body = self.cast_to(body, &widened);
}
let returned = self.plan().expr_type(body).clone();
let accumulator = common_type(&widened, &returned)
.ok_or_else(|| no_common_type(&widened, &returned, true))?;
(body, accumulator)
} else if widened != element {
let body = self.bind_lambda_body(ast, body, scope, table, names, typed(&widened))?;
let returned = self.plan().expr_type(body).clone();
let accumulator = common_type(&element, &returned)
.ok_or_else(|| no_common_type(&element, &returned, false))?;
(body, accumulator)
} else {
(first, widened)
};
if !(2..=3).contains(&count) {
return Err(Error::binder("list_reduce expects a function with 2 or 3 arguments"));
}
if self.plan().expr_type(body) != &accumulator {
body = self.cast_to(body, &accumulator);
}
let initial = initial.map(|initial| {
if self.plan().expr_type(initial) == &accumulator {
initial
} else {
self.cast_to(initial, &accumulator)
}
});
Ok((body, accumulator, initial))
}
fn no_lambda_match(
&mut self,
ast: &Ast,
recorded: &str,
arguments: &[ast::ExprRef],
scope: &Scope,
) -> Error {
let mut types = Vec::with_capacity(arguments.len());
for &arg in arguments {
if any_lambda(ast, arg) {
types.push("LAMBDA".to_string());
continue;
}
match self.bind_expr(ast, arg, scope) {
Ok(bound) => types.push(self.plan().expr_type(bound).to_string()),
Err(error) => return error,
}
}
let candidates = if recorded == INVOKE {
"\tinvoke(col0 LAMBDA, col1 ANY, [ANY...]) -> ANY\n".to_string()
} else if recorded == REDUCE {
"\tlist_reduce(col0 ANY[], col1 LAMBDA) -> ANY\n\tlist_reduce(col0 ANY[], col1 LAMBDA, \
col2 ANY) -> ANY\n"
.to_string()
} else {
format!("\t{recorded}(col0 ANY[], col1 LAMBDA) -> ANY[]\n")
};
Error::binder(format!(
"No function matches the given name and argument types '{recorded}({})'. You might \
need to add explicit type casts.\n\tCandidate functions:\n{candidates}",
types.join(", ")
))
}
}
fn lambda_parts(ast: &Ast, lambda: ast::ExprRef) -> Result<(Vec<String>, ast::ExprRef)> {
let ast::Expr::Lambda { params, body } = ast.expr(lambda) else {
return Err(Error::binder(
"Deprecated lambda arrow (->) detected. Please transition to the new lambda syntax, \
i.e.., lambda x, i: x + i, before DuckDB's next release.\nUse SET \
lambda_syntax='ENABLE_SINGLE_ARROW' to revert to the deprecated behavior.\nFor more \
information, see https://duckdb.org/docs/current/sql/functions/lambda.html.",
));
};
let names: Vec<String> = ast.name(params).map(str::to_string).collect();
for (at, name) in names.iter().enumerate() {
if names[..at].iter().any(|earlier| rudb_catalog::same_name(earlier, name)) {
return Err(Error::binder(format!(
"table \"0_macro_parameters({})\" has duplicate column name \"{name}\"",
names.join(", ")
)));
}
}
Ok((names, body))
}
fn common_type(left: &LogicalType, right: &LogicalType) -> Option<LogicalType> {
match (left, right) {
(LogicalType::Boolean, number) | (number, LogicalType::Boolean) if number.is_numeric() => {
Some(number.clone())
}
_ => left.promote(right),
}
}
fn no_common_type(start: &LogicalType, returned: &LogicalType, initial: bool) -> Error {
let what = if initial { "initial value type" } else { "list element type" };
Error::binder(format!(
"No common super type between {what} {start} and lambda return type {returned}"
))
}
fn has_decimal(ty: &LogicalType) -> bool {
matches!(ty, LogicalType::Decimal { .. }) || ty.children().iter().any(has_decimal)
}