linkrs_query/executor/expression/evaluator/
collection_operations.rs1use crate::executor::expression::ExpressionError;
6use linkrs_core::value::list::List;
7use linkrs_core::Value;
8use serde_json::Value as JsonValue;
9
10fn 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
39fn 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
70fn 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
97pub struct CollectionOperationEvaluator;
99
100impl CollectionOperationEvaluator {
101 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 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 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 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 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 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}