1use std::cmp::Ordering;
36use std::collections::{BTreeMap, BTreeSet};
37use std::time::Instant;
38
39use lora_analyzer::symbols::VarId;
40use lora_analyzer::{ResolvedExpr, ResolvedSortItem};
41use lora_ast::{Direction, RangeLiteral};
42use lora_compiler::physical::ProjectionExec;
43use lora_store::{GraphStorage, NodeId, Properties, PropertyValue};
44
45use crate::errors::{value_kind, ExecResult, ExecutorError};
46use crate::eval::{eval_expr, eval_expr_result, eval_truthy_result, EvalContext};
47use crate::value::{lora_value_to_property, LoraPath, LoraValue, Row};
48
49#[inline]
53pub(super) fn check_deadline_at(deadline: Instant) -> ExecResult<()> {
54 if Instant::now() >= deadline {
55 Err(ExecutorError::QueryTimeout)
56 } else {
57 Ok(())
58 }
59}
60
61pub(super) fn filter_rows_checked<S: GraphStorage>(
62 input_rows: Vec<Row>,
63 predicate: &ResolvedExpr,
64 eval_ctx: &EvalContext<'_, S>,
65) -> ExecResult<Vec<Row>> {
66 let mut out = Vec::with_capacity(input_rows.len());
67 for row in input_rows {
68 if eval_truthy_result(predicate, &row, eval_ctx).map_err(ExecutorError::RuntimeError)? {
69 out.push(row);
70 }
71 }
72 Ok(out)
73}
74
75pub(super) fn project_rows_checked<S: GraphStorage>(
76 input_rows: Vec<Row>,
77 op: &ProjectionExec,
78 eval_ctx: &EvalContext<'_, S>,
79) -> ExecResult<Vec<Row>> {
80 let mut out = Vec::with_capacity(input_rows.len());
81
82 for row in input_rows {
83 if op.include_existing {
84 let mut projected = row;
85 for item in &op.items {
86 let value = eval_expr_result(&item.expr, &projected, eval_ctx)
87 .map_err(ExecutorError::RuntimeError)?;
88 projected.insert_named(item.output, item.name.clone(), value);
89 }
90 out.push(projected);
91 } else {
92 let mut projected = Row::new();
93 for item in &op.items {
94 let value = eval_expr_result(&item.expr, &row, eval_ctx)
95 .map_err(ExecutorError::RuntimeError)?;
96 projected.insert_named(item.output, item.name.clone(), value);
97 }
98 out.push(projected);
99 }
100 }
101
102 Ok(if op.distinct {
103 dedup_rows_by_vars(out)
104 } else {
105 out
106 })
107}
108
109pub(super) fn properties_to_value_map(props: &Properties) -> LoraValue {
110 let mut map = BTreeMap::new();
111 for (k, v) in props.iter() {
112 map.insert(k.clone(), LoraValue::from(v));
113 }
114 LoraValue::Map(map)
115}
116
117pub(crate) fn dedup_rows_by_vars(rows: Vec<Row>) -> Vec<Row> {
121 let mut seen: BTreeSet<Vec<GroupValueKey>> = BTreeSet::new();
122 let mut out = Vec::new();
123
124 for row in rows {
125 let key: Vec<GroupValueKey> = row
126 .iter()
127 .map(|(_, val)| GroupValueKey::from_value(val))
128 .collect();
129 if seen.insert(key) {
130 out.push(row);
131 }
132 }
133
134 out
135}
136
137pub(crate) fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
141 let mut seen: BTreeSet<Vec<(String, GroupValueKey)>> = BTreeSet::new();
142 let mut out = Vec::new();
143
144 for row in rows {
145 let key: Vec<(String, GroupValueKey)> = row
146 .iter_named()
147 .map(|(_, name, val)| (name.into_owned(), GroupValueKey::from_value(val)))
148 .collect();
149 if seen.insert(key) {
150 out.push(row);
151 }
152 }
153
154 out
155}
156
157pub(super) fn eval_properties_expr<S: GraphStorage>(
158 expr: &ResolvedExpr,
159 row: &Row,
160 storage: &S,
161 params: &BTreeMap<String, LoraValue>,
162) -> ExecResult<Properties> {
163 let eval_ctx = EvalContext { storage, params };
164
165 match eval_expr(expr, row, &eval_ctx) {
166 LoraValue::Map(map) => {
167 let mut out = Properties::new();
168 for (k, v) in map {
169 let prop = lora_value_to_property(v)
170 .map_err(|e| ExecutorError::RuntimeError(e.to_string()))?;
171 out.insert(k, prop);
172 }
173 Ok(out)
174 }
175 other => Err(ExecutorError::ExpectedPropertyMap {
176 found: value_kind(&other),
177 }),
178 }
179}
180
181pub(crate) fn compute_aggregate_expr<S: GraphStorage>(
182 expr: &ResolvedExpr,
183 rows: &[Row],
184 eval_ctx: &EvalContext<'_, S>,
185) -> ExecResult<LoraValue> {
186 match expr {
187 ResolvedExpr::Function {
188 name,
189 distinct,
190 args,
191 } => {
192 let func = name.to_ascii_lowercase();
193
194 match func.as_str() {
195 "count" => {
196 if args.is_empty() {
197 return Ok(LoraValue::Int(rows.len() as i64));
198 }
199
200 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
201 values.retain(|v| !matches!(v, LoraValue::Null));
202
203 if *distinct {
204 values = dedup_values(values);
205 }
206
207 Ok(LoraValue::Int(values.len() as i64))
208 }
209
210 "collect" => {
211 if args.is_empty() {
212 return Ok(LoraValue::List(Vec::new()));
213 }
214
215 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
216
217 if *distinct {
218 values = dedup_values(values);
219 }
220
221 Ok(LoraValue::List(values))
222 }
223
224 "sum" => {
225 if args.is_empty() {
226 return Ok(LoraValue::Null);
227 }
228
229 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
230
231 if *distinct {
232 values = dedup_values(values);
233 }
234
235 let nums = values
236 .into_iter()
237 .filter_map(as_f64_lossy)
238 .collect::<Vec<_>>();
239
240 if nums.is_empty() {
241 Ok(LoraValue::Null)
242 } else if nums.iter().all(|n| n.fract() == 0.0) {
243 Ok(LoraValue::Int(nums.iter().sum::<f64>() as i64))
244 } else {
245 Ok(LoraValue::Float(nums.iter().sum::<f64>()))
246 }
247 }
248
249 "avg" => {
250 if args.is_empty() {
251 return Ok(LoraValue::Null);
252 }
253
254 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
255
256 if *distinct {
257 values = dedup_values(values);
258 }
259
260 let nums = values
261 .into_iter()
262 .filter_map(as_f64_lossy)
263 .collect::<Vec<_>>();
264
265 if nums.is_empty() {
266 Ok(LoraValue::Null)
267 } else {
268 Ok(LoraValue::Float(
269 nums.iter().sum::<f64>() / nums.len() as f64,
270 ))
271 }
272 }
273
274 "min" => {
275 if args.is_empty() {
276 return Ok(LoraValue::Null);
277 }
278
279 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
280 values.retain(|v| !matches!(v, LoraValue::Null));
281
282 if *distinct {
283 values = dedup_values(values);
284 }
285
286 Ok(values
287 .into_iter()
288 .min_by(compare_values_total)
289 .unwrap_or(LoraValue::Null))
290 }
291
292 "max" => {
293 if args.is_empty() {
294 return Ok(LoraValue::Null);
295 }
296
297 let mut values = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?;
298 values.retain(|v| !matches!(v, LoraValue::Null));
299
300 if *distinct {
301 values = dedup_values(values);
302 }
303
304 Ok(values
305 .into_iter()
306 .max_by(compare_values_total)
307 .unwrap_or(LoraValue::Null))
308 }
309
310 "stdev" | "stdevp" => {
311 if args.is_empty() {
312 return Ok(LoraValue::Null);
313 }
314
315 let nums: Vec<f64> = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?
316 .into_iter()
317 .filter_map(as_f64_lossy)
318 .collect();
319
320 let is_population = func == "stdevp";
321
322 if nums.is_empty() || (!is_population && nums.len() < 2) {
323 return Ok(LoraValue::Float(0.0));
324 }
325
326 let mean = nums.iter().sum::<f64>() / nums.len() as f64;
327 let variance_sum: f64 = nums.iter().map(|x| (x - mean).powi(2)).sum();
328 let denom = if is_population {
329 nums.len() as f64
330 } else {
331 (nums.len() - 1) as f64
332 };
333 Ok(LoraValue::Float((variance_sum / denom).sqrt()))
334 }
335
336 "percentilecont" => {
337 if args.len() < 2 {
338 return Ok(LoraValue::Null);
339 }
340
341 let Some(first) = rows.first() else {
342 return Ok(LoraValue::Null);
343 };
344
345 let percentile = eval_expr_result(&args[1], first, eval_ctx)
346 .map_err(ExecutorError::RuntimeError)?
347 .as_f64()
348 .unwrap_or(0.5);
349 let mut nums: Vec<f64> = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?
350 .into_iter()
351 .filter_map(as_f64_lossy)
352 .collect();
353
354 if nums.is_empty() {
355 return Ok(LoraValue::Null);
356 }
357
358 nums.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
359
360 let index = percentile * (nums.len() - 1) as f64;
361 let lower = index.floor() as usize;
362 let upper = index.ceil() as usize;
363 let fraction = index - lower as f64;
364
365 if lower == upper || upper >= nums.len() {
366 Ok(LoraValue::Float(nums[lower]))
367 } else {
368 Ok(LoraValue::Float(
369 nums[lower] * (1.0 - fraction) + nums[upper] * fraction,
370 ))
371 }
372 }
373
374 "percentiledisc" => {
375 if args.len() < 2 {
376 return Ok(LoraValue::Null);
377 }
378
379 let Some(first) = rows.first() else {
380 return Ok(LoraValue::Null);
381 };
382
383 let percentile = eval_expr_result(&args[1], first, eval_ctx)
384 .map_err(ExecutorError::RuntimeError)?
385 .as_f64()
386 .unwrap_or(0.5);
387 let mut nums: Vec<f64> = eval_aggregate_arg_values(&args[0], rows, eval_ctx)?
388 .into_iter()
389 .filter_map(as_f64_lossy)
390 .collect();
391
392 if nums.is_empty() {
393 return Ok(LoraValue::Null);
394 }
395
396 nums.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
397
398 let index = (percentile * (nums.len() - 1) as f64).round() as usize;
399 let index = index.min(nums.len() - 1);
400 Ok(LoraValue::Float(nums[index]))
401 }
402
403 _ => eval_first_or_null(expr, rows, eval_ctx),
404 }
405 }
406
407 _ => eval_first_or_null(expr, rows, eval_ctx),
408 }
409}
410
411fn eval_aggregate_arg_values<S: GraphStorage>(
412 expr: &ResolvedExpr,
413 rows: &[Row],
414 eval_ctx: &EvalContext<'_, S>,
415) -> ExecResult<Vec<LoraValue>> {
416 rows.iter()
417 .map(|row| eval_expr_result(expr, row, eval_ctx).map_err(ExecutorError::RuntimeError))
418 .collect()
419}
420
421fn eval_first_or_null<S: GraphStorage>(
422 expr: &ResolvedExpr,
423 rows: &[Row],
424 eval_ctx: &EvalContext<'_, S>,
425) -> ExecResult<LoraValue> {
426 match rows.first() {
427 Some(row) => eval_expr_result(expr, row, eval_ctx).map_err(ExecutorError::RuntimeError),
428 None => Ok(LoraValue::Null),
429 }
430}
431
432pub(crate) fn compare_sort_item<S: GraphStorage>(
433 item: &ResolvedSortItem,
434 a: &Row,
435 b: &Row,
436 eval_ctx: &EvalContext<'_, S>,
437) -> Ordering {
438 let av = eval_expr(&item.expr, a, eval_ctx);
439 let bv = eval_expr(&item.expr, b, eval_ctx);
440
441 let ascending = matches!(item.direction, lora_ast::SortDirection::Asc);
442 compare_values_for_sort(&av, &bv, ascending)
443}
444
445fn dedup_values(values: Vec<LoraValue>) -> Vec<LoraValue> {
446 let mut seen: BTreeSet<GroupValueKey> = BTreeSet::new();
447 let mut out = Vec::new();
448
449 for value in values {
450 let key = GroupValueKey::from_value(&value);
451 if seen.insert(key) {
452 out.push(value);
453 }
454 }
455
456 out
457}
458
459fn as_f64_lossy(v: LoraValue) -> Option<f64> {
460 match v {
461 LoraValue::Int(i) => Some(i as f64),
462 LoraValue::Float(f) => Some(f),
463 _ => None,
464 }
465}
466
467fn compare_values_for_sort(a: &LoraValue, b: &LoraValue, ascending: bool) -> Ordering {
468 let ord = match (a, b) {
469 (LoraValue::Null, LoraValue::Null) => Ordering::Equal,
470 (LoraValue::Null, _) => Ordering::Greater,
471 (_, LoraValue::Null) => Ordering::Less,
472 _ => compare_values_total(a, b),
473 };
474
475 if ascending {
476 ord
477 } else {
478 ord.reverse()
479 }
480}
481
482fn compare_values_total(a: &LoraValue, b: &LoraValue) -> Ordering {
483 use LoraValue::*;
484
485 match (a, b) {
486 (Bool(x), Bool(y)) => x.cmp(y),
487 (Int(x), Int(y)) => x.cmp(y),
488 (Float(x), Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
489 (Int(x), Float(y)) => (*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal),
490 (Float(x), Int(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal),
491 (String(x), String(y)) => x.cmp(y),
492 (Binary(x), Binary(y)) => x.segments().cmp(y.segments()),
493 (Node(x), Node(y)) => x.cmp(y),
494 (Relationship(x), Relationship(y)) => x.cmp(y),
495 (Date(x), Date(y)) => x.cmp(y),
496 (DateTime(x), DateTime(y)) => x.cmp(y),
497 (Duration(x), Duration(y)) => x.cmp(y),
498 (Vector(x), Vector(y)) => x.to_key_string().cmp(&y.to_key_string()),
499 _ => type_rank(a)
500 .cmp(&type_rank(b))
501 .then_with(|| format!("{a:?}").cmp(&format!("{b:?}"))),
502 }
503}
504
505pub fn value_matches_property_value(expected: &LoraValue, actual: &PropertyValue) -> bool {
506 match (expected, actual) {
507 (LoraValue::Null, PropertyValue::Null) => true,
508 (LoraValue::Bool(a), PropertyValue::Bool(b)) => a == b,
509 (LoraValue::Int(a), PropertyValue::Int(b)) => a == b,
510 (LoraValue::Float(a), PropertyValue::Float(b)) => a == b,
511 (LoraValue::Int(a), PropertyValue::Float(b)) => (*a as f64) == *b,
512 (LoraValue::Float(a), PropertyValue::Int(b)) => *a == (*b as f64),
513 (LoraValue::String(a), PropertyValue::String(b)) => a == b,
514 (LoraValue::Binary(a), PropertyValue::Binary(b)) => a == b,
515
516 (LoraValue::List(xs), PropertyValue::List(ys)) => {
517 xs.len() == ys.len()
518 && xs
519 .iter()
520 .zip(ys.iter())
521 .all(|(x, y)| value_matches_property_value(x, y))
522 }
523
524 (LoraValue::Map(xm), PropertyValue::Map(ym)) => xm.iter().all(|(k, xv)| {
525 ym.get(k)
526 .map(|yv| value_matches_property_value(xv, yv))
527 .unwrap_or(false)
528 }),
529
530 (LoraValue::Date(a), PropertyValue::Date(b)) => a == b,
531 (LoraValue::DateTime(a), PropertyValue::DateTime(b)) => a == b,
532 (LoraValue::LocalDateTime(a), PropertyValue::LocalDateTime(b)) => a == b,
533 (LoraValue::Time(a), PropertyValue::Time(b)) => a == b,
534 (LoraValue::LocalTime(a), PropertyValue::LocalTime(b)) => a == b,
535 (LoraValue::Duration(a), PropertyValue::Duration(b)) => a == b,
536 (LoraValue::Point(a), PropertyValue::Point(b)) => a == b,
537 (LoraValue::Vector(a), PropertyValue::Vector(b)) => a == b,
538
539 _ => false,
540 }
541}
542
543pub(crate) fn node_matches_property_filter<S: GraphStorage>(
544 storage: &S,
545 node_id: NodeId,
546 labels: &[Vec<String>],
547 key: &str,
548 expected: &LoraValue,
549) -> bool {
550 storage
551 .with_node(node_id, |node| {
552 node_matches_label_groups(&node.labels, labels)
553 && node
554 .properties
555 .get(key)
556 .map(|actual| value_matches_property_value(expected, actual))
557 .unwrap_or(false)
558 })
559 .unwrap_or(false)
560}
561
562fn single_label_hint(labels: &[Vec<String>]) -> Option<&str> {
563 if labels.len() == 1 && labels[0].len() == 1 {
564 Some(labels[0][0].as_str())
565 } else {
566 None
567 }
568}
569
570fn property_lookup_values(expected: &LoraValue) -> Option<Vec<PropertyValue>> {
571 let property = lora_value_to_property(expected.clone()).ok()?;
572 let mut values = vec![property.clone()];
573
574 match property {
575 PropertyValue::Int(i) => {
576 values.push(PropertyValue::Float(i as f64));
577 }
578 PropertyValue::Float(f)
579 if f.is_finite()
580 && f.fract() == 0.0
581 && f >= i64::MIN as f64
582 && f <= i64::MAX as f64 =>
583 {
584 values.push(PropertyValue::Int(f as i64));
585 }
586 _ => {}
587 }
588
589 Some(values)
590}
591
592pub(crate) struct NodePropertyCandidates {
593 pub(crate) ids: Vec<NodeId>,
594 pub(crate) prefiltered: bool,
595}
596
597pub(crate) fn indexed_node_property_candidates<S: GraphStorage>(
598 storage: &S,
599 labels: &[Vec<String>],
600 key: &str,
601 expected: &LoraValue,
602) -> NodePropertyCandidates {
603 let Some(values) = property_lookup_values(expected) else {
604 return NodePropertyCandidates {
605 ids: scan_node_ids_for_label_groups(storage, labels),
606 prefiltered: false,
607 };
608 };
609
610 let label_hint = single_label_hint(labels);
611 let mut seen = BTreeSet::new();
612 let mut out = Vec::new();
613 for value in values {
614 for id in storage.find_node_ids_by_property(label_hint, key, &value) {
615 if seen.insert(id) {
616 out.push(id);
617 }
618 }
619 }
620 NodePropertyCandidates {
621 ids: out,
622 prefiltered: labels.is_empty() || label_hint.is_some(),
623 }
624}
625
626pub(crate) fn build_path_value<S: GraphStorage>(
632 row: &Row,
633 node_vars: &[VarId],
634 rel_vars: &[VarId],
635 storage: &S,
636) -> LoraValue {
637 let mut raw_nodes = Vec::new();
638 let mut rels = Vec::new();
639 let mut has_var_len = false;
640
641 for &nv in node_vars {
642 match row.get(nv) {
643 Some(LoraValue::Node(id)) => raw_nodes.push(*id),
644 Some(LoraValue::List(items)) => {
645 for item in items {
646 if let LoraValue::Node(id) = item {
647 raw_nodes.push(*id);
648 }
649 }
650 }
651 _ => {}
652 }
653 }
654
655 for &rv in rel_vars {
656 match row.get(rv) {
657 Some(LoraValue::Relationship(id)) => rels.push(*id),
658 Some(LoraValue::List(items)) => {
659 has_var_len = true;
660 for item in items {
661 if let LoraValue::Relationship(id) = item {
662 rels.push(*id);
663 }
664 }
665 }
666 _ => {}
667 }
668 }
669
670 let nodes = if has_var_len && !rels.is_empty() && raw_nodes.len() == 2 {
674 let start = raw_nodes[0];
675 let mut ordered = Vec::with_capacity(rels.len() + 1);
676 ordered.push(start);
677 let mut current = start;
678 for &rel_id in &rels {
679 if let Some((src, dst)) = storage.relationship_endpoints(rel_id) {
680 let next = if src == current { dst } else { src };
681 ordered.push(next);
682 current = next;
683 }
684 }
685 ordered
686 } else {
687 raw_nodes
688 };
689
690 LoraValue::Path(LoraPath { nodes, rels })
691}
692
693fn type_rank(v: &LoraValue) -> u8 {
694 match v {
695 LoraValue::Null => 0,
696 LoraValue::Bool(_) => 1,
697 LoraValue::Int(_) | LoraValue::Float(_) => 2,
698 LoraValue::String(_) => 3,
699 LoraValue::Binary(_) => 4,
700 LoraValue::Date(_) => 5,
701 LoraValue::DateTime(_) => 6,
702 LoraValue::LocalDateTime(_) => 7,
703 LoraValue::Time(_) => 8,
704 LoraValue::LocalTime(_) => 9,
705 LoraValue::Duration(_) => 10,
706 LoraValue::Point(_) => 11,
707 LoraValue::Vector(_) => 12,
708 LoraValue::List(_) => 13,
709 LoraValue::Map(_) => 14,
710 LoraValue::Node(_) => 15,
711 LoraValue::Relationship(_) => 16,
712 LoraValue::Path(_) => 17,
713 }
714}
715
716pub(crate) fn node_matches_label_groups(node_labels: &[String], groups: &[Vec<String>]) -> bool {
720 groups
721 .iter()
722 .all(|group| group.iter().any(|l| node_labels.iter().any(|nl| nl == l)))
723}
724
725pub(crate) fn scan_node_ids_for_label_groups<S: GraphStorage>(
728 storage: &S,
729 groups: &[Vec<String>],
730) -> Vec<NodeId> {
731 if groups.is_empty() {
732 storage.all_node_ids()
733 } else if groups.len() == 1 && groups[0].len() == 1 {
734 storage.node_ids_by_label(&groups[0][0])
735 } else if groups.len() == 1 && groups[0].len() > 1 {
736 let mut seen = BTreeSet::new();
737 let mut out = Vec::new();
738 for label in &groups[0] {
739 for id in storage.node_ids_by_label(label) {
740 if seen.insert(id) {
741 out.push(id);
742 }
743 }
744 }
745 out
746 } else {
747 storage.node_ids_by_label(&groups[0][0])
748 }
749}
750
751pub(crate) fn label_group_candidates_prefiltered(groups: &[Vec<String>]) -> bool {
752 groups.len() <= 1
753}
754
755pub(crate) fn hydrate_node_record(node: &lora_store::NodeRecord) -> LoraValue {
756 let mut map = BTreeMap::new();
757 map.insert("kind".to_string(), LoraValue::String("node".to_string()));
758 map.insert("id".to_string(), LoraValue::Int(node.id as i64));
759 map.insert(
760 "labels".to_string(),
761 LoraValue::List(
762 node.labels
763 .iter()
764 .map(|s| LoraValue::String(s.clone()))
765 .collect(),
766 ),
767 );
768 map.insert(
769 "properties".to_string(),
770 properties_to_value_map(&node.properties),
771 );
772 LoraValue::Map(map)
773}
774
775pub(crate) fn hydrate_relationship_record(rel: &lora_store::RelationshipRecord) -> LoraValue {
776 let mut map = BTreeMap::new();
777 map.insert(
778 "kind".to_string(),
779 LoraValue::String("relationship".to_string()),
780 );
781 map.insert("id".to_string(), LoraValue::Int(rel.id as i64));
782 map.insert("startId".to_string(), LoraValue::Int(rel.src as i64));
783 map.insert("endId".to_string(), LoraValue::Int(rel.dst as i64));
784 map.insert("type".to_string(), LoraValue::String(rel.rel_type.clone()));
785 map.insert(
786 "properties".to_string(),
787 properties_to_value_map(&rel.properties),
788 );
789 LoraValue::Map(map)
790}
791
792pub(super) fn flatten_label_groups(groups: &[Vec<String>]) -> Vec<String> {
795 groups.iter().flat_map(|g| g.iter().cloned()).collect()
796}
797
798#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
799pub(crate) enum GroupValueKey {
800 Null,
801 Bool(bool),
802 Int(i64),
803 Float(String),
804 String(String),
805 Binary(Vec<Vec<u8>>),
806 List(Vec<GroupValueKey>),
807 Map(Vec<(String, GroupValueKey)>),
808 Node(u64),
809 Relationship(u64),
810}
811
812impl GroupValueKey {
813 pub(crate) fn from_value(v: &LoraValue) -> Self {
814 match v {
815 LoraValue::Null => Self::Null,
816 LoraValue::Bool(x) => Self::Bool(*x),
817 LoraValue::Int(x) => Self::Int(*x),
818 LoraValue::Float(x) => Self::Float(x.to_string()),
819 LoraValue::String(x) => Self::String(x.clone()),
820 LoraValue::Binary(x) => Self::Binary(x.segments().to_vec()),
821 LoraValue::List(xs) => Self::List(xs.iter().map(Self::from_value).collect()),
822 LoraValue::Map(m) => Self::Map(
823 m.iter()
824 .map(|(k, v)| (k.clone(), Self::from_value(v)))
825 .collect(),
826 ),
827 LoraValue::Node(id) => Self::Node(*id),
828 LoraValue::Relationship(id) => Self::Relationship(*id),
829 LoraValue::Path(_) => Self::Null,
830 LoraValue::Date(d) => Self::String(d.to_string()),
832 LoraValue::DateTime(dt) => Self::String(dt.to_string()),
833 LoraValue::LocalDateTime(dt) => Self::String(dt.to_string()),
834 LoraValue::Time(t) => Self::String(t.to_string()),
835 LoraValue::LocalTime(t) => Self::String(t.to_string()),
836 LoraValue::Duration(dur) => Self::String(dur.to_string()),
837 LoraValue::Point(p) => Self::String(p.to_string()),
838 LoraValue::Vector(v) => Self::String(format!("vector:{}", v.to_key_string())),
839 }
840 }
841}
842
843const MAX_VAR_LEN_HOPS: u64 = 100;
855
856pub(crate) fn resolve_range(range: &RangeLiteral) -> (u64, u64) {
857 let min_hops = range.start.unwrap_or(1);
858 let max_hops = range.end.unwrap_or(MAX_VAR_LEN_HOPS);
859 (min_hops, max_hops)
860}
861
862pub(crate) struct VarLenResult {
864 pub(crate) dst_node_id: NodeId,
866 pub(crate) rel_ids: Vec<u64>,
868}
869
870pub(crate) fn variable_length_expand<S: GraphStorage>(
877 storage: &S,
878 start_node_id: NodeId,
879 direction: Direction,
880 types: &[String],
881 min_hops: u64,
882 max_hops: u64,
883) -> Vec<VarLenResult> {
884 let mut results = Vec::new();
885
886 let mut frontier: Vec<(NodeId, Vec<u64>)> = vec![(start_node_id, Vec::new())];
888
889 for depth in 1..=max_hops {
890 let is_last_hop = depth == max_hops;
894 let mut next_frontier: Vec<(NodeId, Vec<u64>)> = Vec::new();
895
896 for (current_node, rels_used) in &frontier {
897 for (rel_id, neighbor_id) in storage.expand_ids(*current_node, direction, types) {
900 if rels_used.contains(&rel_id) {
903 continue;
904 }
905
906 if is_last_hop {
907 if depth >= min_hops {
910 let mut rel_ids = Vec::with_capacity(rels_used.len() + 1);
911 rel_ids.extend_from_slice(rels_used);
912 rel_ids.push(rel_id);
913 results.push(VarLenResult {
914 dst_node_id: neighbor_id,
915 rel_ids,
916 });
917 }
918 continue;
919 }
920
921 let mut new_rels = Vec::with_capacity(rels_used.len() + 1);
922 new_rels.extend_from_slice(rels_used);
923 new_rels.push(rel_id);
924
925 if depth >= min_hops {
926 results.push(VarLenResult {
927 dst_node_id: neighbor_id,
928 rel_ids: new_rels.clone(),
929 });
930 }
931
932 next_frontier.push((neighbor_id, new_rels));
933 }
934 }
935
936 if is_last_hop || next_frontier.is_empty() {
937 break;
938 }
939
940 frontier = next_frontier;
941 }
942
943 if min_hops == 0 {
945 results.insert(
946 0,
947 VarLenResult {
948 dst_node_id: start_node_id,
949 rel_ids: Vec::new(),
950 },
951 );
952 }
953
954 results
955}
956
957pub(crate) fn filter_shortest_paths(rows: Vec<Row>, path_var: VarId, all: bool) -> Vec<Row> {
960 if rows.is_empty() {
961 return rows;
962 }
963
964 let lengths: Vec<usize> = rows
966 .iter()
967 .map(|row| match row.get(path_var) {
968 Some(LoraValue::Path(p)) => p.rels.len(),
969 _ => usize::MAX,
970 })
971 .collect();
972
973 let min_len = lengths.iter().copied().min().unwrap_or(usize::MAX);
974
975 let mut result: Vec<Row> = rows
976 .into_iter()
977 .zip(lengths.iter())
978 .filter(|(_, len)| **len == min_len)
979 .map(|(row, _)| row)
980 .collect();
981
982 if !all && result.len() > 1 {
983 result.truncate(1);
984 }
985
986 result
987}