use crate::binder::scope::BinderScope;
use linkrs_core::types::expr::Expression;
use linkrs_core::types::operators::{AggregateFunction, BinaryOperator, UnaryOperator};
use linkrs_core::DataType;
pub struct ExpressionBinder<'a> {
scope: &'a BinderScope,
}
impl<'a> ExpressionBinder<'a> {
pub fn new(scope: &'a BinderScope) -> Self {
Self { scope }
}
pub fn resolve_type(&self, expr: &Expression) -> DataType {
match expr {
Expression::Literal(v) => v.data_type(),
Expression::Variable(name) => self
.scope
.lookup(name)
.and_then(|v| v.properties.values().next().map(|vt| vt.to_data_type()))
.unwrap_or(DataType::Unknown),
Expression::Property { object, property } => {
if let Expression::Variable(var_name) = object.as_ref() {
self.resolve_property_type(var_name, property)
} else {
DataType::Unknown
}
}
Expression::StructField { base, field } => match self.resolve_type(base) {
DataType::Struct(info) => info
.fields
.iter()
.find(|(name, _)| name == field)
.map(|(_, field_type)| field_type.clone())
.unwrap_or(DataType::Unknown),
_ => DataType::Unknown,
},
Expression::Binary { op, left, right } => {
let left_type = self.resolve_type(left);
let right_type = self.resolve_type(right);
match op {
BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessThan
| BinaryOperator::LessThanOrEqual
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterThanOrEqual
| BinaryOperator::And
| BinaryOperator::Or => DataType::Bool,
BinaryOperator::StringConcat => DataType::String,
BinaryOperator::Add
| BinaryOperator::Subtract
| BinaryOperator::Multiply
| BinaryOperator::Divide
| BinaryOperator::Modulo
| BinaryOperator::Exponent => {
self.deduce_arithmetic_type(&left_type, &right_type)
}
_ => DataType::Unknown,
}
}
Expression::Unary { op, operand } => match op {
UnaryOperator::Not => DataType::Bool,
UnaryOperator::IsNull | UnaryOperator::IsNotNull => DataType::Bool,
UnaryOperator::Plus | UnaryOperator::Minus => self.resolve_type(operand),
UnaryOperator::IsEmpty | UnaryOperator::IsNotEmpty => DataType::Bool,
},
Expression::Function { name, args } => {
if name.eq_ignore_ascii_case("list_reduce") {
for arg in args {
if let Expression::Lambda { body, .. } = arg.as_expr() {
let body_type = self.resolve_type(body);
if body_type != DataType::Unknown {
return body_type;
}
break;
}
}
if let Some(initial) = args.get(2) {
let initial_type = self.resolve_type(initial.as_expr());
if initial_type != DataType::Unknown {
return initial_type;
}
}
return DataType::Unknown;
}
let arg_types: Vec<DataType> = args
.iter()
.map(|a| self.resolve_type(a.as_expr()))
.collect();
self.deduce_function_return_type(name, &arg_types)
}
Expression::Aggregate { func, args, .. } => {
let arg_type = args
.first()
.map(|a| self.resolve_type(a))
.unwrap_or(DataType::Unknown);
self.deduce_aggregate_return_type(func, &arg_type)
}
Expression::List(items) => {
DataType::List(Box::new(self.resolve_container_element(items)))
}
Expression::Map(entries) => {
DataType::Map(Box::new(self.resolve_map_value_type(entries)))
}
Expression::Case {
conditions,
default,
..
} => {
for (_, value) in conditions {
let t = self.resolve_type(value);
if t != DataType::Unknown {
return t;
}
}
default
.as_ref()
.map(|d| self.resolve_type(d))
.unwrap_or(DataType::Unknown)
}
Expression::Subscript { collection, .. } => match self.resolve_type(collection) {
DataType::List(element) => element.as_ref().clone(),
DataType::Map(value) => value.as_ref().clone(),
DataType::Set(element) => element.as_ref().clone(),
DataType::Array(info) => info.element.as_ref().clone(),
DataType::Path => DataType::Vertex,
_ => DataType::Unknown,
},
Expression::TypeCast { target_type, .. } => target_type.clone(),
Expression::ListComprehension { map, .. } => DataType::List(Box::new(
map.as_ref()
.map(|m| self.resolve_type(m))
.unwrap_or(DataType::Unknown),
)),
Expression::Reduce {
initial, mapping, ..
} => {
let initial_type = self.resolve_type(initial);
let mapping_type = self.resolve_type(mapping);
if initial_type != DataType::Unknown {
initial_type
} else if mapping_type != DataType::Unknown {
mapping_type
} else {
DataType::Unknown
}
}
Expression::WindowFunction { name, args, .. } => {
let arg_types: Vec<DataType> = args.iter().map(|a| self.resolve_type(a)).collect();
self.deduce_function_return_type(name, &arg_types)
}
Expression::Parameter(_) => DataType::Unknown,
Expression::SessionVariable(_) => DataType::Unknown,
Expression::Vector(v) => DataType::VectorDense(v.len()),
Expression::Path(_) => DataType::Path,
Expression::PathBuild(_) => DataType::Path,
Expression::Label(_) => DataType::String,
Expression::TagProperty { .. } => DataType::Unknown,
Expression::EdgeProperty { .. } => DataType::Unknown,
Expression::LabelTagProperty { .. } => DataType::Unknown,
Expression::Predicate { .. } => DataType::Bool,
Expression::Range { collection, .. } => match self.resolve_type(collection) {
DataType::List(element) => DataType::List(element),
DataType::Array(info) => DataType::Array(info),
_ => DataType::List(Box::new(DataType::Unknown)),
},
Expression::Exists { .. } => DataType::Bool,
Expression::In { .. } => DataType::Bool,
Expression::CountSubquery { .. } => DataType::Int,
Expression::ScalarSubquery { .. } => DataType::Unknown,
Expression::Lambda { .. } => DataType::Unknown,
}
}
pub fn resolve_property_type(&self, var_name: &str, property: &str) -> DataType {
if let Some(var_info) = self.scope.lookup(var_name) {
if let Some(vt) = var_info.properties.get(property) {
return vt.to_data_type();
}
}
DataType::Unknown
}
fn resolve_container_element(&self, items: &[Expression]) -> DataType {
let mut common = DataType::Unknown;
for item in items {
let item_type = self.resolve_type(item);
common = if common == DataType::Unknown {
item_type
} else {
linkrs_core::type_system::TypeUtils::get_common_type(&common, &item_type)
};
if common == DataType::Empty {
return DataType::Unknown;
}
}
common
}
fn resolve_map_value_type(&self, entries: &[(String, Expression)]) -> DataType {
let mut common = DataType::Unknown;
for (_, value) in entries {
let value_type = self.resolve_type(value);
common = if common == DataType::Unknown {
value_type
} else {
linkrs_core::type_system::TypeUtils::get_common_type(&common, &value_type)
};
if common == DataType::Empty {
return DataType::Unknown;
}
}
common
}
pub fn deduce_arithmetic_type(&self, left: &DataType, right: &DataType) -> DataType {
let left_is_numeric = matches!(
left,
DataType::SmallInt
| DataType::Int
| DataType::BigInt
| DataType::Float
| DataType::Double
);
let right_is_numeric = matches!(
right,
DataType::SmallInt
| DataType::Int
| DataType::BigInt
| DataType::Float
| DataType::Double
);
if !left_is_numeric || !right_is_numeric {
return DataType::Unknown;
}
let left_is_float = matches!(left, DataType::Float | DataType::Double);
let right_is_float = matches!(right, DataType::Float | DataType::Double);
if left_is_float || right_is_float {
DataType::Float
} else {
DataType::Int
}
}
pub fn deduce_function_return_type(&self, name: &str, arg_types: &[DataType]) -> DataType {
let lower = name.to_lowercase();
match lower.as_str() {
"list_reduce" => return DataType::Unknown,
"head" | "last" | "coalesce" | "greatest" | "least" | "nullif" | "ifnull"
| "first_value" | "last_value" | "nth_value" | "lead" | "lag" | "element_at"
| "list_extract" | "struct_extract" | "map_extract" => {
return arg_types.first().cloned().unwrap_or(DataType::Unknown);
}
"date_add" | "date_sub" | "date_trunc" | "last_day" => {
return arg_types.first().cloned().unwrap_or(DataType::DateTime);
}
_ => {}
}
let registry = crate::executor::expression::functions::global_registry_ref();
if let Some(return_type) = registry.get_return_type(name) {
if *return_type != DataType::Unknown {
return return_type.clone();
}
if !arg_types.is_empty() {
let first = &arg_types[0];
if *first != DataType::Unknown {
return first.clone();
}
}
return return_type.clone();
}
match lower.as_str() {
"head" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
"last" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
"coalesce" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
_ => DataType::Unknown,
}
}
pub fn deduce_aggregate_return_type(
&self,
func: &AggregateFunction,
arg_type: &DataType,
) -> DataType {
match func {
AggregateFunction::Count => DataType::Int,
AggregateFunction::Sum => DataType::Float,
AggregateFunction::Avg => DataType::Float,
AggregateFunction::Max | AggregateFunction::Min => arg_type.clone(),
AggregateFunction::Collect => DataType::List(Box::new(arg_type.clone())),
AggregateFunction::CollectSet => DataType::Set(Box::new(arg_type.clone())),
AggregateFunction::Percentile => DataType::Float,
AggregateFunction::PercentileCont => DataType::Float,
AggregateFunction::Std => DataType::Float,
AggregateFunction::StddevPop => DataType::Float,
AggregateFunction::StddevSamp => DataType::Float,
AggregateFunction::Product => DataType::Float,
AggregateFunction::Variance => DataType::Float,
AggregateFunction::Median => DataType::Float,
AggregateFunction::Mode => arg_type.clone(),
AggregateFunction::BitAnd | AggregateFunction::BitOr => DataType::Int,
AggregateFunction::BoolAnd | AggregateFunction::BoolOr => DataType::Bool,
AggregateFunction::GroupConcat => DataType::String,
AggregateFunction::GroupConcatWithOrder => DataType::String,
AggregateFunction::VecSum => DataType::Vector,
AggregateFunction::VecAvg => DataType::Vector,
}
}
}