use rudb_common::{Error, Field, LogicalType, Result, Value};
use rudb_plan::{Expr, ExprRef};
use crate::binder::Binder;
fn parts(ty: &LogicalType) -> Option<(LogicalType, LogicalType)> {
match ty {
LogicalType::Map(key, value) => Some(((**key).clone(), (**value).clone())),
LogicalType::Null => Some((LogicalType::Null, LogicalType::Null)),
_ => None,
}
}
fn element(ty: &LogicalType) -> Option<LogicalType> {
match ty {
LogicalType::List(element) => Some((**element).clone()),
LogicalType::Null => Some(LogicalType::Null),
_ => None,
}
}
fn map_of(key: LogicalType, value: LogicalType) -> LogicalType {
LogicalType::Map(Box::new(key), Box::new(value))
}
fn entry(key: LogicalType, value: LogicalType) -> LogicalType {
LogicalType::Struct(vec![Field::new("key", key), Field::new("value", value)])
}
pub(crate) fn no_match(written: &str, types: &[LogicalType], candidates: &[&str]) -> Error {
let types: Vec<String> = types.iter().map(ToString::to_string).collect();
let mut message = format!(
"No function matches the given name and argument types '{}({})'. You might need to add \
explicit type casts.\n\tCandidate functions:",
written.to_lowercase(),
types.join(", ")
);
for candidate in candidates {
message.push_str("\n\t");
message.push_str(candidate);
}
Error::binder(message)
}
impl Binder<'_> {
pub(crate) fn map_call(&mut self, written: &str, bound: &[ExprRef]) -> Result<Option<ExprRef>> {
let name = written.to_ascii_lowercase();
let types: Vec<LogicalType> =
bound.iter().map(|&arg| self.plan().expr_type(arg).clone()).collect();
let subscript = matches!(
name.as_str(),
"array_extract" | "list_extract" | "list_element" | "struct_extract"
);
if subscript && !matches!(types.first(), Some(LogicalType::Map(_, _))) {
return Ok(None);
}
let name = if subscript { "map_extract_value" } else { name.as_str() };
match name {
"map" => self.bind_map(bound, &types).map(Some),
"map_from_entries" => {
let entries = match types.as_slice() {
[LogicalType::List(entry)] => match &**entry {
LogicalType::Struct(fields) if fields.len() == 2 => {
Some((fields[0].ty.clone(), fields[1].ty.clone()))
}
LogicalType::Null => Some((LogicalType::Null, LogicalType::Null)),
_ => None,
},
[LogicalType::Null] => Some((LogicalType::Null, LogicalType::Null)),
_ => None,
};
let Some((key, value)) = entries else {
return Err(no_match(
written,
&types,
&["map_from_entries(col0 TUPLE(K, V)[]) -> MAP(K, V)"],
));
};
Ok(Some(self.record(name, bound, map_of(key, value))))
}
"map_keys" | "map_values" | "map_entries" | "cardinality" => {
let Some((key, value)) = types.first().and_then(parts).filter(|_| bound.len() == 1)
else {
return Ok(None);
};
let returns = match name {
"map_keys" => LogicalType::list(key),
"map_values" => LogicalType::list(value),
"map_entries" => LogicalType::list(entry(key, value)),
_ => LogicalType::UBigInt,
};
Ok(Some(self.record(name, bound, returns)))
}
"map_extract" | "element_at" | "map_extract_value" | "map_contains" => {
let [map, key] = bound else {
return Ok(None);
};
let Some((key_type, value)) = parts(&types[0]) else {
return Ok(None);
};
let key = self.map_key(name, *key, &key_type)?;
let returns = match name {
"map_extract_value" => value,
"map_contains" => LogicalType::Boolean,
_ => LogicalType::list(value),
};
let recorded = if name == "element_at" { "map_extract" } else { name };
Ok(Some(self.record(recorded, &[*map, key], returns)))
}
"map_contains_value" => {
let [map, needle] = bound else {
return Ok(None);
};
let Some((_, value)) = parts(&types[0]) else {
return Ok(None);
};
let needle = self.checked_cast_to(*needle, &value, false)?;
Ok(Some(self.record(name, &[*map, needle], LogicalType::Boolean)))
}
"map_contains_entry" => {
let [map, key, needle] = bound else {
return Ok(None);
};
let Some((key_type, value)) = parts(&types[0]) else {
return Ok(None);
};
let key = self.map_key(name, *key, &key_type)?;
let needle = self.checked_cast_to(*needle, &value, false)?;
Ok(Some(self.record(name, &[*map, key, needle], LogicalType::Boolean)))
}
"map_concat" => {
let mut returns: Option<LogicalType> = None;
for ty in &types {
if *ty == LogicalType::Null {
continue;
}
if !matches!(ty, LogicalType::Map(_, _)) {
return Ok(None);
}
match &returns {
None => returns = Some(ty.clone()),
Some(first) if first != ty => {
return Err(Error::invalid_input(format!(
"'value' type of map differs between arguments, expected \
'{first}', found '{ty}' instead"
)));
}
Some(_) => {}
}
}
let returns =
returns.unwrap_or_else(|| map_of(LogicalType::Null, LogicalType::Null));
Ok(Some(self.record(name, bound, returns)))
}
_ => Ok(None),
}
}
fn bind_map(&mut self, bound: &[ExprRef], types: &[LogicalType]) -> Result<ExprRef> {
let candidates =
["\"map\"() -> MAP(\"NULL\", \"NULL\")", "\"map\"(col0 K[], col1 V[]) -> MAP(K, V)"];
match (bound, types) {
([], []) => Ok(self.add_constant(Value::Map {
key: Box::new(LogicalType::Null),
value: Box::new(LogicalType::Null),
entries: Vec::new(),
})),
([_, _], [keys, values]) => {
let (Some(key), Some(value)) = (element(keys), element(values)) else {
return Err(no_match("map", types, &candidates));
};
Ok(self.record("map", bound, map_of(key, value)))
}
_ => Err(no_match("map", types, &candidates)),
}
}
fn map_key(&mut self, name: &str, key: ExprRef, key_type: &LogicalType) -> Result<ExprRef> {
let written = self.plan().expr_type(key).clone();
if *key_type == LogicalType::Varchar
&& !matches!(written, LogicalType::Varchar | LogicalType::Null)
{
let template = if name == "map_extract_value" {
"map_extract_value(MAP(K, V), K) -> V".to_string()
} else {
format!("{name}(MAP(K, V), K)")
};
return Err(Error::binder(format!(
"Cannot deduce template type 'K' in function: '{template}'\nType 'K' was inferred \
to be:\n - 'VARCHAR', from first occurrence\n - '{written}', which is \
incompatible with previously inferred type!"
)));
}
if *key_type == LogicalType::Null {
return Ok(key);
}
self.checked_cast_to(key, key_type, false)
}
fn record(&mut self, name: &str, args: &[ExprRef], returns: LogicalType) -> ExprRef {
let args = self.plan_mut().add_expr_list(args);
let recorded = self.plan_mut().intern(name);
self.add_expr(Expr::Function { name: recorded, args }, returns)
}
}