use rudb_common::{Error, Field, LogicalType, Result, Value};
use rudb_parse::{Ast, ast};
use rudb_plan::{ColumnBinding, Expr, ExprRef, Node, NodeRef};
use crate::binder::Binder;
use crate::fold;
use crate::scope::Scope;
use crate::structs::STRUCT_EXTRACT;
#[derive(Debug, Clone, Copy)]
pub(crate) struct UnnestStruct {
pub(crate) depth: usize,
pub(crate) keep_parent_names: bool,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GroupedUnnest {
arg: ExprRef,
depth: usize,
column: ExprRef,
}
#[derive(Debug, Clone)]
pub(crate) struct UnnestCall {
arg: ExprRef,
depth: usize,
}
fn element(ty: &LogicalType) -> Option<&LogicalType> {
match ty {
LogicalType::List(element) | LogicalType::Array(element, _) => Some(element),
_ => None,
}
}
fn nesting(ty: &LogicalType) -> usize {
element(ty).map_or(0, |inner| 1 + nesting(inner))
}
fn element_at(ty: &LogicalType, depth: usize) -> LogicalType {
let mut at = ty;
for _ in 0..depth {
match element(at) {
Some(inner) => at = inner,
None => break,
}
}
at.clone()
}
impl Binder<'_> {
pub(crate) fn bind_unnest(
&mut self,
ast: &Ast,
call: ast::ExprRef,
args: &[ast::ExprRef],
scope: &Scope,
) -> Result<ExprRef> {
if self.in_lambda() {
return Err(Error::binder("UNNEST in lambda expressions is not supported"));
}
if self.in_unnest {
return Err(Error::binder(
"Nested UNNEST calls are not supported - use UNNEST(x, recursive := true) to \
unnest multiple levels",
));
}
if !self.unnest_here || self.in_aggregate || self.in_window {
return Err(Error::binder("UNNEST not supported here"));
}
let [arg] = args else {
if args.is_empty() {
return Err(Error::binder("UNNEST() requires at least one argument"));
}
return Err(Error::binder(
"UNNEST - unsupported extra argument, unnest only supports recursive := \
[true/false], max_depth := # or keep_parent_names := [true/false]",
));
};
let root = std::mem::take(&mut self.unnest_root);
let mut recursive = false;
let mut keep_parent_names = false;
let mut max_depth = None;
for target in ast.named_args(call).to_vec() {
let name = ast.string(target.alias).to_ascii_lowercase();
let wanted = match name.as_str() {
"recursive" | "keep_parent_names" => LogicalType::Boolean,
"max_depth" => LogicalType::BigInt,
_ => {
return Err(Error::binder(format!(
"Unsupported parameter \"{}\" for unnest",
ast.string(target.alias)
)));
}
};
let bound = self.bind_expr(ast, target.expr, scope)?;
let bound = self.checked_cast_to(bound, &wanted, false)?;
let value = match fold::value_of(self.plan(), bound)? {
Some(Value::Null) => {
return Err(Error::binder(format!(
"UNNEST parameter \"{name}\" cannot be NULL"
)));
}
Some(value) => value,
None => {
return Err(Error::binder(format!(
"UNNEST parameter \"{name}\" has to be a constant"
)));
}
};
match (name.as_str(), value) {
("recursive", Value::Boolean(on)) => recursive = on,
("keep_parent_names", Value::Boolean(on)) => keep_parent_names = on,
("max_depth", Value::BigInt(0)) => {
return Err(Error::binder("UNNEST cannot have a max depth of 0"));
}
("max_depth", Value::BigInt(depth)) => {
max_depth = Some(usize::try_from(depth).map_err(|_| {
Error::binder(format!("UNNEST cannot have a max depth of {depth}"))
})?);
}
_ => {}
}
}
let outer = std::mem::replace(&mut self.in_unnest, true);
let bound = self.bind_expr(ast, *arg, scope);
self.in_unnest = outer;
let bound = bound?;
let ty = self.plan().expr_type(bound).clone();
if !matches!(
ty,
LogicalType::List(_)
| LogicalType::Array(..)
| LogicalType::Struct(_)
| LogicalType::Null
) {
return Err(Error::binder(format!(
"UNNEST() can only be applied to lists, structs and NULL, not {ty}"
)));
}
let allowed = match max_depth {
Some(most) => most,
None if recursive => usize::MAX,
None => 1,
};
let lists = allowed.min(nesting(&ty));
let produced = element_at(&ty, lists);
let structs = allowed - lists;
let expands = structs > 0 && matches!(produced, LogicalType::Struct(_));
match self.unnest_grouping {
Some(true) => return Err(Error::binder("Cannot group on an UNNEST or UNLIST clause")),
Some(false) if expands => {
return Err(Error::binder("UNNEST of struct cannot be used in GROUP BY clause"));
}
_ => {}
}
if expands {
if !root {
return Err(Error::binder(
"UNNEST() on a struct column can only be applied as the root element of a \
SELECT expression",
));
}
self.unnest_struct = Some(UnnestStruct { depth: structs, keep_parent_names });
}
if lists == 0 && !matches!(ty, LogicalType::Null) {
return Ok(bound);
}
let depth = lists.max(1);
if self.unnest_grouping.is_none() {
let grouped = self.grouped_unnests.clone();
if let Some(found) =
grouped.iter().find(|held| held.depth == depth && self.same_expr(held.arg, bound))
{
return Ok(found.column);
}
}
let written = bound;
let bound = self.over_aggregate(bound, scope)?;
let arg = match &ty {
LogicalType::Array(inner, _) => {
self.checked_cast_to(bound, &LogicalType::List(inner.clone()), false)?
}
_ => bound,
};
let index = match self.unnest_index {
Some(index) => index,
None => {
let index = self.fresh_index();
self.unnest_index = Some(index);
index
}
};
let position = self.unnests.len();
self.unnests.push(UnnestCall { arg, depth });
let binding = ColumnBinding::new(index, position as u32);
let column = self.add_expr(Expr::Column(binding), produced);
if self.unnest_grouping.is_some() {
self.grouped_unnests.push(GroupedUnnest { arg: written, depth, column });
}
Ok(column)
}
pub(crate) fn unnest_fields(
&mut self,
input: ExprRef,
taking: UnnestStruct,
prefix: Option<&str>,
exprs: &mut Vec<ExprRef>,
names: &mut Vec<String>,
) -> Result<()> {
let LogicalType::Struct(fields) = self.plan().expr_type(input).clone() else {
return Err(Error::internal("a struct unnest of something that is not a struct"));
};
let recorded = self.plan_mut().intern(STRUCT_EXTRACT);
for (at, field) in fields.iter().enumerate() {
let key = self.add_constant(Value::BigInt(at as i64 + 1));
let args = self.plan_mut().add_expr_list(&[input, key]);
let expr = self.add_expr(Expr::Function { name: recorded, args }, field.ty.clone());
let own = if field.name.is_empty() {
format!("element{}", at + 1)
} else {
field.name.clone()
};
let name = match prefix {
Some(prefix) if taking.keep_parent_names => format!("{prefix}.{own}"),
_ => own,
};
if taking.depth > 1 && matches!(field.ty, LogicalType::Struct(_)) {
let deeper = UnnestStruct { depth: taking.depth - 1, ..taking };
let prefix = (!field.name.is_empty()).then_some(name.as_str());
self.unnest_fields(expr, deeper, prefix, exprs, names)?;
} else {
exprs.push(expr);
names.push(name);
}
}
Ok(())
}
pub(crate) fn is_unnest_output(&self, binding: ColumnBinding) -> bool {
self.unnest_index == Some(binding.table)
}
pub(crate) fn plan_unnests(
&mut self,
mut node: NodeRef,
index: u32,
calls: &[UnnestCall],
) -> Result<NodeRef> {
let deepest = calls.iter().map(|call| call.depth).max().unwrap_or(1);
let function = self.plan_mut().intern("unnest");
let options = self.plan_mut().add_name_list(&[]);
let settings = self.plan_mut().add_expr_list(&[]);
let mut current: Vec<Option<ExprRef>> = vec![None; calls.len()];
for level in 1..=deepest {
let at = if level == deepest { index } else { self.fresh_index() };
let mut args = Vec::new();
let mut fields = Vec::new();
let mut taking = Vec::new();
for (which, call) in calls.iter().enumerate() {
if deepest - call.depth + 1 > level {
continue;
}
let mut arg = current[which].unwrap_or(call.arg);
if let LogicalType::Array(inner, _) = self.plan().expr_type(arg).clone() {
arg = self.checked_cast_to(arg, &LogicalType::List(inner), false)?;
}
let produced =
element(self.plan().expr_type(arg)).cloned().unwrap_or(LogicalType::Null);
args.push(arg);
fields.push(Field::new("unnest", produced));
taking.push(which);
}
for (position, (&which, field)) in taking.iter().zip(&fields).enumerate() {
let binding = ColumnBinding::new(at, position as u32);
current[which] = Some(self.add_expr(Expr::Column(binding), field.ty.clone()));
}
let args = self.plan_mut().add_expr_list(&args);
let columns = self.plan_mut().add_fields(&fields);
node = self.add_node(Node::LateralFunction {
input: node,
index: at,
function,
args,
options,
settings,
columns,
});
}
Ok(node)
}
}