Skip to main content

linkrs_query/binder/
expr_binder.rs

1//! Expression-level type deduction for the Binder.
2//!
3//! This module provides **stateless** type deduction: given an `Expression`
4//! and the current binding scope, it returns the resolved `DataType`.
5//!
6//! Unlike the Validator's `ExpressionAnalyzer`, it does **not** mutate the
7//! `ExpressionAnalysisContext`. It is a pure function from `Expression` →
8//! `DataType`, designed to be called from `Binder::bind_inner_expr()`.
9
10use crate::binder::scope::BinderScope;
11use linkrs_core::types::expr::Expression;
12use linkrs_core::types::operators::{AggregateFunction, BinaryOperator, UnaryOperator};
13use linkrs_core::DataType;
14
15/// Stateless expression type deduction.
16pub struct ExpressionBinder<'a> {
17    scope: &'a BinderScope,
18}
19
20impl<'a> ExpressionBinder<'a> {
21    pub fn new(scope: &'a BinderScope) -> Self {
22        Self { scope }
23    }
24
25    /// Resolve the type of an expression.
26    pub fn resolve_type(&self, expr: &Expression) -> DataType {
27        match expr {
28            Expression::Literal(v) => v.data_type(),
29
30            Expression::Variable(name) => self
31                .scope
32                .lookup(name)
33                .and_then(|v| v.properties.values().next().map(|vt| vt.to_data_type()))
34                .unwrap_or(DataType::Unknown),
35
36            Expression::Property { object, property } => {
37                if let Expression::Variable(var_name) = object.as_ref() {
38                    self.resolve_property_type(var_name, property)
39                } else {
40                    DataType::Unknown
41                }
42            }
43
44            Expression::StructField { base, field } => match self.resolve_type(base) {
45                DataType::Struct(info) => info
46                    .fields
47                    .iter()
48                    .find(|(name, _)| name == field)
49                    .map(|(_, field_type)| field_type.clone())
50                    .unwrap_or(DataType::Unknown),
51                _ => DataType::Unknown,
52            },
53
54            Expression::Binary { op, left, right } => {
55                let left_type = self.resolve_type(left);
56                let right_type = self.resolve_type(right);
57                match op {
58                    BinaryOperator::Equal
59                    | BinaryOperator::NotEqual
60                    | BinaryOperator::LessThan
61                    | BinaryOperator::LessThanOrEqual
62                    | BinaryOperator::GreaterThan
63                    | BinaryOperator::GreaterThanOrEqual
64                    | BinaryOperator::And
65                    | BinaryOperator::Or => DataType::Bool,
66                    BinaryOperator::StringConcat => DataType::String,
67                    BinaryOperator::Add
68                    | BinaryOperator::Subtract
69                    | BinaryOperator::Multiply
70                    | BinaryOperator::Divide
71                    | BinaryOperator::Modulo
72                    | BinaryOperator::Exponent => {
73                        self.deduce_arithmetic_type(&left_type, &right_type)
74                    }
75                    _ => DataType::Unknown,
76                }
77            }
78
79            Expression::Unary { op, operand } => match op {
80                UnaryOperator::Not => DataType::Bool,
81                UnaryOperator::IsNull | UnaryOperator::IsNotNull => DataType::Bool,
82                UnaryOperator::Plus | UnaryOperator::Minus => self.resolve_type(operand),
83                UnaryOperator::IsEmpty | UnaryOperator::IsNotEmpty => DataType::Bool,
84            },
85
86            Expression::Function { name, args } => {
87                if name.eq_ignore_ascii_case("list_reduce") {
88                    // Prefer the lambda body type over the initial value so
89                    // `reduce` over strings, decimals or nested lists keeps
90                    // the body result instead of falling back to the list type.
91                    for arg in args {
92                        if let Expression::Lambda { body, .. } = arg.as_expr() {
93                            let body_type = self.resolve_type(body);
94                            if body_type != DataType::Unknown {
95                                return body_type;
96                            }
97                            break;
98                        }
99                    }
100                    if let Some(initial) = args.get(2) {
101                        let initial_type = self.resolve_type(initial.as_expr());
102                        if initial_type != DataType::Unknown {
103                            return initial_type;
104                        }
105                    }
106                    return DataType::Unknown;
107                }
108                let arg_types: Vec<DataType> = args
109                    .iter()
110                    .map(|a| self.resolve_type(a.as_expr()))
111                    .collect();
112                self.deduce_function_return_type(name, &arg_types)
113            }
114
115            Expression::Aggregate { func, args, .. } => {
116                let arg_type = args
117                    .first()
118                    .map(|a| self.resolve_type(a))
119                    .unwrap_or(DataType::Unknown);
120                self.deduce_aggregate_return_type(func, &arg_type)
121            }
122
123            Expression::List(items) => {
124                DataType::List(Box::new(self.resolve_container_element(items)))
125            }
126            Expression::Map(entries) => {
127                DataType::Map(Box::new(self.resolve_map_value_type(entries)))
128            }
129
130            Expression::Case {
131                conditions,
132                default,
133                ..
134            } => {
135                for (_, value) in conditions {
136                    let t = self.resolve_type(value);
137                    if t != DataType::Unknown {
138                        return t;
139                    }
140                }
141                default
142                    .as_ref()
143                    .map(|d| self.resolve_type(d))
144                    .unwrap_or(DataType::Unknown)
145            }
146
147            Expression::Subscript { collection, .. } => match self.resolve_type(collection) {
148                DataType::List(element) => element.as_ref().clone(),
149                DataType::Map(value) => value.as_ref().clone(),
150                DataType::Set(element) => element.as_ref().clone(),
151                DataType::Array(info) => info.element.as_ref().clone(),
152                DataType::Path => DataType::Vertex,
153                _ => DataType::Unknown,
154            },
155
156            Expression::TypeCast { target_type, .. } => target_type.clone(),
157
158            Expression::ListComprehension { map, .. } => DataType::List(Box::new(
159                map.as_ref()
160                    .map(|m| self.resolve_type(m))
161                    .unwrap_or(DataType::Unknown),
162            )),
163            // The REDUCE result type is the accumulator type, determined by
164            // the initial value and the mapping expression.
165            Expression::Reduce {
166                initial, mapping, ..
167            } => {
168                let initial_type = self.resolve_type(initial);
169                let mapping_type = self.resolve_type(mapping);
170                if initial_type != DataType::Unknown {
171                    initial_type
172                } else if mapping_type != DataType::Unknown {
173                    mapping_type
174                } else {
175                    DataType::Unknown
176                }
177            }
178            // Window functions derive their return type from the underlying
179            // function name and argument types (same table as scalar calls).
180            Expression::WindowFunction { name, args, .. } => {
181                let arg_types: Vec<DataType> = args.iter().map(|a| self.resolve_type(a)).collect();
182                self.deduce_function_return_type(name, &arg_types)
183            }
184
185            Expression::Parameter(_) => DataType::Unknown,
186            Expression::SessionVariable(_) => DataType::Unknown,
187            Expression::Vector(v) => DataType::VectorDense(v.len()),
188
189            Expression::Path(_) => DataType::Path,
190            Expression::PathBuild(_) => DataType::Path,
191            Expression::Label(_) => DataType::String,
192            Expression::TagProperty { .. } => DataType::Unknown,
193            Expression::EdgeProperty { .. } => DataType::Unknown,
194            Expression::LabelTagProperty { .. } => DataType::Unknown,
195            Expression::Predicate { .. } => DataType::Bool,
196            Expression::Range { collection, .. } => match self.resolve_type(collection) {
197                DataType::List(element) => DataType::List(element),
198                DataType::Array(info) => DataType::Array(info),
199                _ => DataType::List(Box::new(DataType::Unknown)),
200            },
201            Expression::Exists { .. } => DataType::Bool,
202            Expression::In { .. } => DataType::Bool,
203            Expression::CountSubquery { .. } => DataType::Int,
204            Expression::ScalarSubquery { .. } => DataType::Unknown,
205            Expression::Lambda { .. } => DataType::Unknown,
206        }
207    }
208
209    /// Resolve property type from scope, falling back to Unknown.
210    pub fn resolve_property_type(&self, var_name: &str, property: &str) -> DataType {
211        if let Some(var_info) = self.scope.lookup(var_name) {
212            if let Some(vt) = var_info.properties.get(property) {
213                return vt.to_data_type();
214            }
215        }
216        DataType::Unknown
217    }
218
219    /// Common element type of an `Expression::List` literal (falls back to
220    /// `Unknown` for an empty/heterogeneous list).
221    fn resolve_container_element(&self, items: &[Expression]) -> DataType {
222        let mut common = DataType::Unknown;
223        for item in items {
224            let item_type = self.resolve_type(item);
225            common = if common == DataType::Unknown {
226                item_type
227            } else {
228                linkrs_core::type_system::TypeUtils::get_common_type(&common, &item_type)
229            };
230            if common == DataType::Empty {
231                return DataType::Unknown;
232            }
233        }
234        common
235    }
236
237    /// Common value type of an `Expression::Map` literal (falls back to
238    /// `Unknown` for an empty/heterogeneous map).
239    fn resolve_map_value_type(&self, entries: &[(String, Expression)]) -> DataType {
240        let mut common = DataType::Unknown;
241        for (_, value) in entries {
242            let value_type = self.resolve_type(value);
243            common = if common == DataType::Unknown {
244                value_type
245            } else {
246                linkrs_core::type_system::TypeUtils::get_common_type(&common, &value_type)
247            };
248            if common == DataType::Empty {
249                return DataType::Unknown;
250            }
251        }
252        common
253    }
254
255    /// Arithmetic type promotion: int + float → float.
256    pub fn deduce_arithmetic_type(&self, left: &DataType, right: &DataType) -> DataType {
257        let left_is_numeric = matches!(
258            left,
259            DataType::SmallInt
260                | DataType::Int
261                | DataType::BigInt
262                | DataType::Float
263                | DataType::Double
264        );
265        let right_is_numeric = matches!(
266            right,
267            DataType::SmallInt
268                | DataType::Int
269                | DataType::BigInt
270                | DataType::Float
271                | DataType::Double
272        );
273
274        if !left_is_numeric || !right_is_numeric {
275            return DataType::Unknown;
276        }
277
278        let left_is_float = matches!(left, DataType::Float | DataType::Double);
279        let right_is_float = matches!(right, DataType::Float | DataType::Double);
280
281        if left_is_float || right_is_float {
282            DataType::Float
283        } else {
284            DataType::Int
285        }
286    }
287
288    /// Deduce function return type from name and argument types.
289    pub fn deduce_function_return_type(&self, name: &str, arg_types: &[DataType]) -> DataType {
290        let lower = name.to_lowercase();
291        match lower.as_str() {
292            // list_reduce without expression context cannot see the lambda
293            // body; the Function branch in resolve_type handles the precise
294            // case. Keep Unknown here so callers do not fall back to the
295            // list element type.
296            "list_reduce" => return DataType::Unknown,
297            "head" | "last" | "coalesce" | "greatest" | "least" | "nullif" | "ifnull"
298            | "first_value" | "last_value" | "nth_value" | "lead" | "lag" | "element_at"
299            | "list_extract" | "struct_extract" | "map_extract" => {
300                return arg_types.first().cloned().unwrap_or(DataType::Unknown);
301            }
302            "date_add" | "date_sub" | "date_trunc" | "last_day" => {
303                return arg_types.first().cloned().unwrap_or(DataType::DateTime);
304            }
305            _ => {}
306        }
307
308        let registry = crate::executor::expression::functions::global_registry_ref();
309        if let Some(return_type) = registry.get_return_type(name) {
310            if *return_type != DataType::Unknown {
311                return return_type.clone();
312            }
313            // For polymorphic functions that still have Unknown in registry,
314            // fall back to first argument type when available.
315            if !arg_types.is_empty() {
316                let first = &arg_types[0];
317                if *first != DataType::Unknown {
318                    return first.clone();
319                }
320            }
321            return return_type.clone();
322        }
323
324        match lower.as_str() {
325            "head" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
326            "last" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
327            "coalesce" => arg_types.first().cloned().unwrap_or(DataType::Unknown),
328            _ => DataType::Unknown,
329        }
330    }
331
332    /// Deduce aggregate function return type.
333    pub fn deduce_aggregate_return_type(
334        &self,
335        func: &AggregateFunction,
336        arg_type: &DataType,
337    ) -> DataType {
338        match func {
339            AggregateFunction::Count => DataType::Int,
340            AggregateFunction::Sum => DataType::Float,
341            AggregateFunction::Avg => DataType::Float,
342            AggregateFunction::Max | AggregateFunction::Min => arg_type.clone(),
343            AggregateFunction::Collect => DataType::List(Box::new(arg_type.clone())),
344            AggregateFunction::CollectSet => DataType::Set(Box::new(arg_type.clone())),
345            AggregateFunction::Percentile => DataType::Float,
346            AggregateFunction::PercentileCont => DataType::Float,
347            AggregateFunction::Std => DataType::Float,
348            AggregateFunction::StddevPop => DataType::Float,
349            AggregateFunction::StddevSamp => DataType::Float,
350            AggregateFunction::Product => DataType::Float,
351            AggregateFunction::Variance => DataType::Float,
352            AggregateFunction::Median => DataType::Float,
353            AggregateFunction::Mode => arg_type.clone(),
354            AggregateFunction::BitAnd | AggregateFunction::BitOr => DataType::Int,
355            AggregateFunction::BoolAnd | AggregateFunction::BoolOr => DataType::Bool,
356            AggregateFunction::GroupConcat => DataType::String,
357            AggregateFunction::GroupConcatWithOrder => DataType::String,
358            AggregateFunction::VecSum => DataType::Vector,
359            AggregateFunction::VecAvg => DataType::Vector,
360        }
361    }
362}