Skip to main content

linkrs_query/executor/expression/evaluator/
collection_operations.rs

1//! Set operation evaluator
2//!
3//! Provide functionality for evaluating collection types, including index access, range access, and property access.
4
5use crate::executor::expression::ExpressionError;
6use linkrs_core::value::list::List;
7use linkrs_core::Value;
8use serde_json::Value as JsonValue;
9
10/// Convert a serde_json::Value to a native Value
11fn json_to_value_inline(json: &JsonValue) -> Value {
12    match json {
13        JsonValue::Null => Value::Null(linkrs_core::value::NullType::Null),
14        JsonValue::Bool(b) => Value::Bool(*b),
15        JsonValue::Number(n) => {
16            if let Some(i) = n.as_i64() {
17                Value::BigInt(i)
18            } else if let Some(f) = n.as_f64() {
19                Value::Double(f)
20            } else {
21                Value::Null(linkrs_core::value::NullType::Null)
22            }
23        }
24        JsonValue::String(s) => Value::string(s.clone()),
25        JsonValue::Array(arr) => {
26            let values: Vec<Value> = arr.iter().map(json_to_value_inline).collect();
27            Value::list(List::from(values))
28        }
29        JsonValue::Object(obj) => {
30            let map: std::collections::HashMap<Value, Value> = obj
31                .iter()
32                .map(|(k, v)| (Value::string(k.clone()), json_to_value_inline(v)))
33                .collect();
34            Value::map(map)
35        }
36    }
37}
38
39/// Access a JSON value by subscript (string key for objects, integer index for arrays)
40fn json_subscript_access(json: &JsonValue, index: &Value) -> Result<Value, ExpressionError> {
41    match (json, index) {
42        (JsonValue::Object(map), Value::String(key)) => map
43            .get(key.as_str())
44            .map(json_to_value_inline)
45            .ok_or_else(|| ExpressionError::runtime_error(format!("JSON key not found: {}", key))),
46        (JsonValue::Array(arr), Value::Int(i)) => {
47            let idx = if *i < 0 { arr.len() as i32 + i } else { *i } as usize;
48            arr.get(idx)
49                .map(json_to_value_inline)
50                .ok_or_else(|| ExpressionError::index_out_of_bounds(idx as isize, arr.len()))
51        }
52        (JsonValue::Array(arr), Value::BigInt(i)) => {
53            let idx = if *i < 0 { arr.len() as i64 + i } else { *i } as usize;
54            arr.get(idx)
55                .map(json_to_value_inline)
56                .ok_or_else(|| ExpressionError::index_out_of_bounds(idx as isize, arr.len()))
57        }
58        (JsonValue::Object(_), _) => Err(ExpressionError::type_error(
59            "JSON object key must be a string",
60        )),
61        (JsonValue::Array(_), _) => Err(ExpressionError::type_error(
62            "JSON array index must be an integer",
63        )),
64        _ => Err(ExpressionError::type_error(
65            "Subscript not supported on this JSON type",
66        )),
67    }
68}
69
70/// Access a JSON value by property name (for property access syntax `obj.key`)
71fn json_property_access(json: &JsonValue, property: &str) -> Result<Value, ExpressionError> {
72    match json {
73        JsonValue::Object(map) => Ok(map
74            .get(property)
75            .map(json_to_value_inline)
76            .unwrap_or(Value::Null(linkrs_core::value::NullType::Null))),
77        JsonValue::Array(arr) => {
78            if let Ok(index) = property.parse::<isize>() {
79                let idx = if index < 0 {
80                    arr.len() as isize + index
81                } else {
82                    index
83                } as usize;
84                arr.get(idx)
85                    .map(json_to_value_inline)
86                    .ok_or_else(|| ExpressionError::index_out_of_bounds(idx as isize, arr.len()))
87            } else {
88                Err(ExpressionError::type_error(
89                    "JSON array property must be an integer index",
90                ))
91            }
92        }
93        _ => Ok(Value::Null(linkrs_core::value::NullType::Null)),
94    }
95}
96
97/// Set operation evaluator
98pub struct CollectionOperationEvaluator;
99
100impl CollectionOperationEvaluator {
101    /// Try to convert the Value to an i64 index.
102    fn value_to_i64(index: &Value) -> Option<i64> {
103        match index {
104            Value::SmallInt(i) => Some(*i as i64),
105            Value::Int(i) => Some(*i as i64),
106            Value::BigInt(i) => Some(*i),
107            _ => None,
108        }
109    }
110
111    /// Index access for evaluation
112    pub fn eval_subscript_access(
113        collection: &Value,
114        index: &Value,
115    ) -> Result<Value, ExpressionError> {
116        if collection.is_null() || index.is_null() {
117            return Ok(Value::Null(linkrs_core::value::NullType::Null));
118        }
119
120        match collection {
121            Value::List(list) => {
122                if let Some(i) = Self::value_to_i64(index) {
123                    let adjusted_index = if i < 0 { list.len() as i64 + i } else { i };
124
125                    if adjusted_index >= 0 && (adjusted_index as usize) < list.len() {
126                        Ok(list[adjusted_index as usize].clone())
127                    } else {
128                        Err(ExpressionError::index_out_of_bounds(
129                            adjusted_index as isize,
130                            list.len(),
131                        ))
132                    }
133                } else {
134                    Err(ExpressionError::type_error(
135                        "List subscripts must be integers",
136                    ))
137                }
138            }
139            Value::String(s) => {
140                if let Some(i) = Self::value_to_i64(index) {
141                    let chars: Vec<char> = s.chars().collect();
142                    let adjusted_index = if i < 0 { chars.len() as i64 + i } else { i };
143
144                    if adjusted_index >= 0 && (adjusted_index as usize) < chars.len() {
145                        Ok(Value::string(chars[adjusted_index as usize].to_string()))
146                    } else {
147                        Err(ExpressionError::index_out_of_bounds(
148                            adjusted_index as isize,
149                            chars.len(),
150                        ))
151                    }
152                } else {
153                    Err(ExpressionError::type_error(
154                        "String subscripts must be integers",
155                    ))
156                }
157            }
158            Value::Map(map) => {
159                if let Value::String(key) = index {
160                    map.get(&Value::string(key.clone()))
161                        .cloned()
162                        .ok_or_else(|| {
163                            ExpressionError::runtime_error(format!(
164                                "Mapping key does not exist: {}",
165                                key
166                            ))
167                        })
168                } else {
169                    map.get(index).cloned().ok_or_else(|| {
170                        ExpressionError::runtime_error(format!(
171                            "Mapping key does not exist: {}",
172                            index
173                        ))
174                    })
175                }
176            }
177            Value::Struct(s) => {
178                if let Value::String(key) = index {
179                    s.fields
180                        .iter()
181                        .find(|(name, _)| name == key)
182                        .map(|(_, value)| value.clone())
183                        .ok_or_else(|| {
184                            ExpressionError::runtime_error(format!(
185                                "STRUCT field does not exist: {}",
186                                key
187                            ))
188                        })
189                } else {
190                    Err(ExpressionError::type_error(
191                        "STRUCT subscript key must be a string",
192                    ))
193                }
194            }
195            Value::Array(a) => {
196                if let Some(i) = Self::value_to_i64(index) {
197                    let adjusted_index = if i < 0 { a.values.len() as i64 + i } else { i };
198                    if adjusted_index >= 0 && (adjusted_index as usize) < a.values.len() {
199                        Ok(a.values[adjusted_index as usize].clone())
200                    } else {
201                        Err(ExpressionError::index_out_of_bounds(
202                            adjusted_index as isize,
203                            a.values.len(),
204                        ))
205                    }
206                } else {
207                    Err(ExpressionError::type_error(
208                        "ARRAY subscripts must be integers",
209                    ))
210                }
211            }
212            Value::Json(j) => {
213                let json_value = j
214                    .to_value()
215                    .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e)))?;
216                json_subscript_access(&json_value, index)
217            }
218            Value::JsonB(j) => json_subscript_access(j.as_value(), index),
219            _ => Err(ExpressionError::type_error(
220                "Types for which subscript access are not supported",
221            )),
222        }
223    }
224
225    /// Access to the evaluation range
226    pub fn eval_range_access(
227        collection: &Value,
228        start: Option<&Value>,
229        end: Option<&Value>,
230    ) -> Result<Value, ExpressionError> {
231        if collection.is_null() {
232            return Ok(Value::Null(linkrs_core::value::NullType::Null));
233        }
234
235        if start.is_some_and(|v| v.is_null()) || end.is_some_and(|v| v.is_null()) {
236            return Ok(Value::Null(linkrs_core::value::NullType::Null));
237        }
238
239        match collection {
240            Value::List(list) => {
241                let start_idx = start
242                    .map(|v| {
243                        if let Value::Int(i) = v {
244                            if *i < 0 {
245                                (list.len() as i32 + i) as usize
246                            } else {
247                                *i as usize
248                            }
249                        } else {
250                            0
251                        }
252                    })
253                    .unwrap_or(0);
254
255                let end_idx = end
256                    .map(|v| {
257                        if let Value::Int(i) = v {
258                            if *i < 0 {
259                                (list.len() as i32 + i) as usize
260                            } else {
261                                *i as usize
262                            }
263                        } else {
264                            list.len()
265                        }
266                    })
267                    .unwrap_or(list.len());
268
269                if start_idx <= end_idx && end_idx <= list.len() {
270                    Ok(Value::list(List::from(list[start_idx..end_idx].to_vec())))
271                } else {
272                    Err(ExpressionError::index_out_of_bounds(
273                        start_idx as isize,
274                        list.len(),
275                    ))
276                }
277            }
278            Value::String(s) => {
279                let chars: Vec<char> = s.chars().collect();
280                let start_idx = start
281                    .map(|v| {
282                        if let Value::Int(i) = v {
283                            if *i < 0 {
284                                (chars.len() as i32 + i) as usize
285                            } else {
286                                *i as usize
287                            }
288                        } else {
289                            0
290                        }
291                    })
292                    .unwrap_or(0);
293
294                let end_idx = end
295                    .map(|v| {
296                        if let Value::Int(i) = v {
297                            if *i < 0 {
298                                (chars.len() as i32 + i) as usize
299                            } else {
300                                *i as usize
301                            }
302                        } else {
303                            chars.len()
304                        }
305                    })
306                    .unwrap_or(chars.len());
307
308                if start_idx <= end_idx && end_idx <= chars.len() {
309                    let result: String = chars[start_idx..end_idx].iter().collect();
310                    Ok(Value::string(result))
311                } else {
312                    Err(ExpressionError::index_out_of_bounds(
313                        start_idx as isize,
314                        chars.len(),
315                    ))
316                }
317            }
318            _ => Err(ExpressionError::type_error(
319                "Types of scope access are not supported",
320            )),
321        }
322    }
323
324    /// Access to the evaluation attribute
325    pub fn eval_property_access(object: &Value, property: &str) -> Result<Value, ExpressionError> {
326        if object.is_null() {
327            return Ok(Value::Null(linkrs_core::value::NullType::Null));
328        }
329
330        match object {
331            Value::Vertex(vertex) => Ok(vertex
332                .property_value(property)
333                .unwrap_or_else(|| Value::Null(linkrs_core::value::NullType::Null))),
334            Value::Edge(edge) => Ok(edge
335                .properties()
336                .get(property)
337                .cloned()
338                .unwrap_or(Value::Null(linkrs_core::value::NullType::Null))),
339            Value::EdgeHeader(_) => Err(ExpressionError::type_error(
340                "Property access on an edge identity reference is not supported: the reference carries no properties",
341            )),
342            Value::Map(map) => Ok(map
343                .get(&Value::string(property))
344                .cloned()
345                .unwrap_or(Value::Null(linkrs_core::value::NullType::Null))),
346            Value::Struct(s) => Ok(s
347                .fields
348                .iter()
349                .find(|(name, _)| name == property)
350                .map(|(_, value)| value.clone())
351                .unwrap_or(Value::Null(linkrs_core::value::NullType::Null))),
352            Value::List(list) => {
353                if let Ok(index) = property.parse::<isize>() {
354                    let adjusted_index = if index < 0 {
355                        list.len() as isize + index
356                    } else {
357                        index
358                    };
359
360                    if adjusted_index >= 0 && adjusted_index < list.len() as isize {
361                        Ok(list[adjusted_index as usize].clone())
362                    } else {
363                        Err(ExpressionError::index_out_of_bounds(
364                            adjusted_index,
365                            list.len(),
366                        ))
367                    }
368                } else {
369                    Err(ExpressionError::type_error("List index must be an integer"))
370                }
371            }
372            Value::Json(j) => {
373                let json_value = j
374                    .to_value()
375                    .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e)))?;
376                json_property_access(&json_value, property)
377            }
378            Value::JsonB(j) => json_property_access(j.as_value(), property),
379            _ => Err(ExpressionError::type_error(
380                "Types of property access are not supported",
381            )),
382        }
383    }
384
385    /// Access to evaluation attributes (Attribute operations, used for BinaryOperator::Attribute)
386    /// Format the value on the right side as a string and use it as the attribute name.
387    pub fn eval_attribute_access(
388        object: &Value,
389        attribute: &Value,
390    ) -> Result<Value, ExpressionError> {
391        let property = format!("{}", attribute);
392        Self::eval_property_access(object, &property)
393    }
394
395    /// STRUCT field access (e.g. `addr.city`).
396    ///
397    /// Resolves a named field of a STRUCT value; missing fields yield NULL.
398    /// Vertex/Edge/Map bases fall back to property-access semantics so
399    /// tag-qualified paths such as `$$.Person.name` resolve correctly.
400    pub fn eval_struct_field_access(object: &Value, field: &str) -> Result<Value, ExpressionError> {
401        if object.is_null() {
402            return Ok(Value::Null(linkrs_core::value::NullType::Null));
403        }
404        match object {
405            Value::Struct(s) => Ok(s
406                .fields
407                .iter()
408                .find(|(name, _)| name == field)
409                .map(|(_, value)| value.clone())
410                .unwrap_or(Value::Null(linkrs_core::value::NullType::Null))),
411            _ => Self::eval_property_access(object, field),
412        }
413    }
414}