Skip to main content

linkrs_query/binder/bound/
expr.rs

1use linkrs_core::types::expr::SubqueryBody;
2use linkrs_core::types::operators::{BinaryOperator, UnaryOperator};
3use linkrs_core::types::semantic::ValueType;
4use linkrs_core::DataType;
5use linkrs_core::Value;
6
7use super::stmt::BoundStatement;
8use crate::binder::query_graph::QueryGraph;
9
10/// A fully resolved expression with known types and catalog references.
11///
12/// Unlike the AST [`Expression`] which carries unresolved names,
13/// every variant here has been type-checked and catalog-resolved.
14#[derive(Debug, Clone)]
15pub enum BoundExpression {
16    /// Literal value with resolved type
17    Literal(Value, DataType),
18
19    /// Variable reference (no property access)
20    Variable(String, DataType),
21
22    /// Property access on an expression (e.g. `n.name`)
23    Property {
24        object: Box<BoundExpression>,
25        property: String,
26        value_type: DataType,
27    },
28
29    /// STRUCT field access (e.g. `addr.city`)
30    StructField {
31        base: Box<BoundExpression>,
32        field: String,
33        return_type: DataType,
34    },
35
36    /// Binary operation with resolved result type
37    BinaryOp {
38        left: Box<BoundExpression>,
39        op: BinaryOperator,
40        right: Box<BoundExpression>,
41        return_type: DataType,
42    },
43
44    /// Unary operation
45    UnaryOp {
46        op: UnaryOperator,
47        operand: Box<BoundExpression>,
48        return_type: DataType,
49    },
50
51    /// Function call with resolved return type
52    Function(BoundFunctionCall),
53
54    /// Aggregate function call
55    Aggregate(BoundAggregateCall),
56
57    /// Parameter reference (`@param`)
58    ParameterRef(String, DataType),
59
60    /// Session variable reference (`$name`), distinct from query parameters.
61    /// Type is dynamic (resolved at execution time from session state).
62    SessionVariable(String, DataType),
63
64    /// Subquery expression
65    Subquery {
66        query: Box<BoundStatement>,
67        original_body: Option<Box<SubqueryBody>>,
68    },
69
70    /// Cast expression
71    Cast {
72        expr: Box<BoundExpression>,
73        target_type: DataType,
74    },
75
76    /// List literal
77    List(Vec<BoundExpression>, DataType),
78
79    /// Map literal
80    Map(Vec<(String, BoundExpression)>, DataType),
81
82    /// Case/when expression
83    Case {
84        expr: Option<Box<BoundExpression>>,
85        when_then: Vec<(BoundExpression, BoundExpression)>,
86        else_expr: Option<Box<BoundExpression>>,
87        return_type: DataType,
88    },
89
90    /// Exists predicate
91    Exists {
92        query: Box<BoundStatement>,
93        original_body: Option<Box<SubqueryBody>>,
94    },
95
96    /// Pattern expression (path pattern in expression context)
97    Pattern(QueryGraph),
98
99    /// Label expression
100    Label(String),
101
102    /// Tag property access
103    TagProperty {
104        tag_name: String,
105        property: String,
106        value_type: DataType,
107    },
108
109    /// Edge property access
110    EdgeProperty {
111        edge_name: String,
112        property: String,
113        value_type: DataType,
114    },
115
116    /// Predicate expression (FILTER, ALL, ANY, etc.)
117    Predicate {
118        func: String,
119        args: Vec<BoundExpression>,
120        return_type: DataType,
121    },
122
123    /// Subscript access (collection[index])
124    Subscript {
125        collection: Box<BoundExpression>,
126        index: Box<BoundExpression>,
127        return_type: DataType,
128    },
129
130    /// Window function
131    WindowFunction {
132        name: String,
133        args: Vec<BoundExpression>,
134        over_partition_by: Vec<BoundExpression>,
135        over_order_by: Vec<BoundExpression>,
136        over_order_desc: Vec<bool>,
137        return_type: DataType,
138    },
139
140    /// IN subquery
141    In {
142        expr: Box<BoundExpression>,
143        subquery: Box<BoundStatement>,
144        negated: bool,
145        original_body: Option<Box<SubqueryBody>>,
146    },
147
148    /// COUNT subquery: evaluates to the number of rows returned by the subquery
149    CountSubquery {
150        query: Box<BoundStatement>,
151        original_body: Option<Box<SubqueryBody>>,
152    },
153
154    /// Lambda expression: anonymous function used as callback for higher-order list functions
155    Lambda {
156        params: Vec<String>,
157        body: Box<BoundExpression>,
158    },
159
160    /// Path expression
161    Path(Vec<BoundExpression>, DataType),
162
163    /// List comprehension
164    ListComprehension {
165        variable: String,
166        source: Box<BoundExpression>,
167        filter: Option<Box<BoundExpression>>,
168        map: Option<Box<BoundExpression>>,
169        return_type: DataType,
170    },
171
172    /// Reduce expression
173    Reduce {
174        accumulator: String,
175        initial: Box<BoundExpression>,
176        variable: String,
177        source: Box<BoundExpression>,
178        mapping: Box<BoundExpression>,
179        return_type: DataType,
180    },
181
182    /// Path build
183    PathBuild(Vec<BoundExpression>, DataType),
184
185    /// Vector literal
186    Vector(Vec<f32>),
187}
188
189/// A function call with resolved arguments.
190#[derive(Debug, Clone)]
191pub struct BoundFunctionCall {
192    pub name: String,
193    pub args: Vec<BoundExpression>,
194    pub return_type: ValueType,
195}
196
197/// Aggregate function call.
198#[derive(Debug, Clone)]
199pub struct BoundAggregateCall {
200    pub function_name: String,
201    pub arguments: Vec<BoundExpression>,
202    pub distinct: bool,
203    pub alias: Option<String>,
204    pub return_type: ValueType,
205}
206
207impl BoundExpression {
208    /// Return the concrete DataType of this expression.
209    pub fn return_type(&self) -> DataType {
210        match self {
211            Self::Literal(_, dt) => dt.clone(),
212            Self::Variable(_, dt) => dt.clone(),
213            Self::Property { value_type, .. } => value_type.clone(),
214            Self::StructField { return_type, .. } => return_type.clone(),
215            Self::BinaryOp { return_type, .. } => return_type.clone(),
216            Self::UnaryOp { return_type, .. } => return_type.clone(),
217            Self::Function(f) => f.return_type.to_data_type(),
218            Self::Aggregate(a) => a.return_type.to_data_type(),
219            Self::ParameterRef(_, dt) => dt.clone(),
220            Self::SessionVariable(_, _) => DataType::Unknown,
221            Self::Subquery { .. } => DataType::Unknown,
222            Self::Cast { target_type, .. } => target_type.clone(),
223            Self::List(_, dt) => dt.clone(),
224            Self::Map(_, dt) => dt.clone(),
225            Self::Case { return_type, .. } => return_type.clone(),
226            Self::Exists { .. } => DataType::Bool,
227            Self::Pattern(_) => DataType::Path,
228            Self::Label(_) => DataType::String,
229            Self::TagProperty { value_type, .. } => value_type.clone(),
230            Self::EdgeProperty { value_type, .. } => value_type.clone(),
231            Self::Predicate { return_type, .. } => return_type.clone(),
232            Self::Subscript { return_type, .. } => return_type.clone(),
233            Self::WindowFunction { return_type, .. } => return_type.clone(),
234            Self::In { .. } => DataType::Bool,
235            Self::CountSubquery { .. } => DataType::Int,
236            Self::Lambda { .. } => DataType::Unknown,
237            Self::Path(_, dt) => dt.clone(),
238            Self::ListComprehension { return_type, .. } => return_type.clone(),
239            Self::Reduce { return_type, .. } => return_type.clone(),
240            Self::PathBuild(_, dt) => dt.clone(),
241            Self::Vector(v) => DataType::VectorDense(v.len()),
242        }
243    }
244
245    /// Get the ValueType of this expression for downstream consumers.
246    pub fn value_type(&self) -> ValueType {
247        ValueType::from_data_type(&self.return_type())
248    }
249}