1use std::collections::HashSet;
2use std::collections::VecDeque;
3
4use crate::id::id_from_value;
5use crate::id::id_into_value;
6use crate::id::RawId;
7use crate::id::ID_LEN;
8use crate::query::intersectionconstraint::IntersectionConstraint;
9use crate::query::Binding;
10use crate::query::Constraint;
11use crate::query::Query;
12use crate::query::TriblePattern;
13use crate::query::Variable;
14use crate::query::VariableContext;
15use crate::query::VariableId;
16use crate::query::VariableSet;
17use crate::trible::TribleSet;
18use crate::inline::encodings::genid::GenId;
19use crate::inline::Inline;
20use crate::inline::RawInline;
21use crate::inline::IntoInline;
22
23#[derive(Clone)]
31pub enum PathOp {
32 Attr(RawId),
34 NotAttr(RawId),
40 Concat,
42 Union,
44 Star,
46 Plus,
48 Optional,
53 Inverse,
60}
61
62#[derive(Clone)]
64enum PathExpr {
65 Attr(RawId),
66 InverseAttr(RawId),
70 NotAttr(RawId),
72 InverseNotAttr(RawId),
74 Concat(Box<PathExpr>, Box<PathExpr>),
75 Union(Box<PathExpr>, Box<PathExpr>),
76 Star(Box<PathExpr>),
77 Plus(Box<PathExpr>),
78 Optional(Box<PathExpr>),
79}
80
81impl PathExpr {
82 fn from_postfix(ops: &[PathOp]) -> Self {
83 let mut stack: Vec<PathExpr> = Vec::new();
84 for op in ops {
85 match op {
86 PathOp::Attr(id) => stack.push(PathExpr::Attr(*id)),
87 PathOp::NotAttr(id) => stack.push(PathExpr::NotAttr(*id)),
88 PathOp::Concat => {
89 let b = stack.pop().unwrap();
90 let a = stack.pop().unwrap();
91 stack.push(PathExpr::Concat(Box::new(a), Box::new(b)));
92 }
93 PathOp::Union => {
94 let b = stack.pop().unwrap();
95 let a = stack.pop().unwrap();
96 stack.push(PathExpr::Union(Box::new(a), Box::new(b)));
97 }
98 PathOp::Star => {
99 let a = stack.pop().unwrap();
100 stack.push(PathExpr::Star(Box::new(a)));
101 }
102 PathOp::Plus => {
103 let a = stack.pop().unwrap();
104 stack.push(PathExpr::Plus(Box::new(a)));
105 }
106 PathOp::Optional => {
107 let a = stack.pop().unwrap();
108 stack.push(PathExpr::Optional(Box::new(a)));
109 }
110 PathOp::Inverse => {
111 let a = stack.pop().unwrap();
112 stack.push(invert(a));
113 }
114 }
115 }
116 normalize(stack.pop().unwrap())
125 }
126
127 fn build_constraint(
130 &self,
131 set: &TribleSet,
132 ctx: &mut VariableContext,
133 start: Variable<GenId>,
134 constraints: &mut Vec<Box<dyn Constraint<'static> + 'static>>,
135 ) -> Variable<GenId> {
136 match self {
137 PathExpr::Attr(attr_id) => {
138 let a = ctx.next_variable::<GenId>();
139 let dest = ctx.next_variable::<GenId>();
140 constraints.push(Box::new(a.is(attr_id.to_inline())));
141 constraints.push(Box::new(set.pattern(start, a, dest)));
142 dest
143 }
144 PathExpr::InverseAttr(attr_id) => {
145 let a = ctx.next_variable::<GenId>();
147 let dest = ctx.next_variable::<GenId>();
148 constraints.push(Box::new(a.is(attr_id.to_inline())));
149 constraints.push(Box::new(set.pattern(dest, a, start)));
150 dest
151 }
152 PathExpr::NotAttr(_) | PathExpr::InverseNotAttr(_) => {
153 unreachable!("negated-attribute hops handled at eval_from level")
161 }
162 PathExpr::Concat(lhs, rhs) => {
163 let mid = lhs.build_constraint(set, ctx, start, constraints);
164 rhs.build_constraint(set, ctx, mid, constraints)
165 }
166 PathExpr::Union(..)
167 | PathExpr::Star(..)
168 | PathExpr::Plus(..)
169 | PathExpr::Optional(..) => {
170 unreachable!("closures, unions, and optionals handled at eval_from level")
171 }
172 }
173 }
174}
175
176fn invert(expr: PathExpr) -> PathExpr {
182 match expr {
183 PathExpr::Attr(a) => PathExpr::InverseAttr(a),
184 PathExpr::InverseAttr(a) => PathExpr::Attr(a),
185 PathExpr::NotAttr(a) => PathExpr::InverseNotAttr(a),
186 PathExpr::InverseNotAttr(a) => PathExpr::NotAttr(a),
187 PathExpr::Concat(lhs, rhs) => PathExpr::Concat(Box::new(invert(*rhs)), Box::new(invert(*lhs))),
189 PathExpr::Union(lhs, rhs) => PathExpr::Union(Box::new(invert(*lhs)), Box::new(invert(*rhs))),
190 PathExpr::Star(body) => PathExpr::Star(Box::new(invert(*body))),
191 PathExpr::Plus(body) => PathExpr::Plus(Box::new(invert(*body))),
192 PathExpr::Optional(body) => PathExpr::Optional(Box::new(invert(*body))),
193 }
194}
195
196fn normalize(expr: PathExpr) -> PathExpr {
204 match expr {
205 PathExpr::Attr(a) => PathExpr::Attr(a),
206 PathExpr::InverseAttr(a) => PathExpr::InverseAttr(a),
207 PathExpr::NotAttr(a) => PathExpr::NotAttr(a),
208 PathExpr::InverseNotAttr(a) => PathExpr::InverseNotAttr(a),
209 PathExpr::Concat(lhs, rhs) => {
210 let l = normalize(*lhs);
211 let r = normalize(*rhs);
212 distribute_concat(l, r)
213 }
214 PathExpr::Union(lhs, rhs) => {
215 PathExpr::Union(Box::new(normalize(*lhs)), Box::new(normalize(*rhs)))
216 }
217 PathExpr::Star(body) => PathExpr::Star(Box::new(normalize(*body))),
218 PathExpr::Plus(body) => PathExpr::Plus(Box::new(normalize(*body))),
219 PathExpr::Optional(body) => PathExpr::Optional(Box::new(normalize(*body))),
220 }
221}
222
223fn distribute_concat(l: PathExpr, r: PathExpr) -> PathExpr {
227 match (l, r) {
228 (PathExpr::Union(a, b), c) => PathExpr::Union(
230 Box::new(distribute_concat(*a, c.clone())),
231 Box::new(distribute_concat(*b, c)),
232 ),
233 (a, PathExpr::Union(b, c)) => PathExpr::Union(
235 Box::new(distribute_concat(a.clone(), *b)),
236 Box::new(distribute_concat(a, *c)),
237 ),
238 (PathExpr::Optional(a), c) => PathExpr::Union(
240 Box::new(c.clone()),
241 Box::new(distribute_concat(*a, c)),
242 ),
243 (a, PathExpr::Optional(b)) => PathExpr::Union(
245 Box::new(a.clone()),
246 Box::new(distribute_concat(a, *b)),
247 ),
248 (l, r) => PathExpr::Concat(Box::new(l), Box::new(r)),
250 }
251}
252
253fn build_join(
256 set: &TribleSet,
257 expr: &PathExpr,
258 start: &RawId,
259) -> (
260 IntersectionConstraint<Box<dyn Constraint<'static>>>,
261 VariableId,
262) {
263 let mut ctx = VariableContext::new();
264 let start_var = ctx.next_variable::<GenId>();
265 let mut constraints: Vec<Box<dyn Constraint<'static> + 'static>> = Vec::new();
266 constraints.push(Box::new(start_var.is(start.to_inline())));
267 let dest_var = expr.build_constraint(set, &mut ctx, start_var, &mut constraints);
268 (IntersectionConstraint::new(constraints), dest_var.index)
269}
270
271fn eval_attr(set: &TribleSet, attr: &RawId, start: &RawId) -> HashSet<RawId> {
278 let mut results = HashSet::new();
279 let mut prefix = [0u8; ID_LEN * 2];
280 prefix[..ID_LEN].copy_from_slice(start);
281 prefix[ID_LEN..].copy_from_slice(attr);
282 set.eav
283 .infixes::<{ ID_LEN * 2 }, 32, _>(&prefix, |value: &[u8; 32]| {
284 if value[..ID_LEN] == [0; ID_LEN] {
285 let dest: RawId = value[ID_LEN..].try_into().unwrap();
286 results.insert(dest);
287 }
288 });
289 results
290}
291
292fn eval_not_attr(set: &TribleSet, excluded: &RawId, start: &RawId) -> HashSet<RawId> {
301 let mut results = HashSet::new();
302 let mut e_prefix = [0u8; ID_LEN];
303 e_prefix.copy_from_slice(start);
304 let mut attrs: Vec<RawId> = Vec::new();
306 set.eav.infixes::<{ ID_LEN }, ID_LEN, _>(&e_prefix, |a: &[u8; ID_LEN]| {
307 if a == excluded {
308 return;
309 }
310 attrs.push(*a);
311 });
312 for attr in attrs {
314 let mut ea_prefix = [0u8; ID_LEN * 2];
315 ea_prefix[..ID_LEN].copy_from_slice(start);
316 ea_prefix[ID_LEN..].copy_from_slice(&attr);
317 set.eav
318 .infixes::<{ ID_LEN * 2 }, 32, _>(&ea_prefix, |value: &[u8; 32]| {
319 if value[..ID_LEN] == [0; ID_LEN] {
320 let dest: RawId = value[ID_LEN..].try_into().unwrap();
321 results.insert(dest);
322 }
323 });
324 }
325 results
326}
327
328fn eval_not_attr_inverse(set: &TribleSet, excluded: &RawId, start: &RawId) -> HashSet<RawId> {
334 let mut results = HashSet::new();
335 let start_value = id_into_value(start);
336 let mut v_prefix = [0u8; 32];
337 v_prefix.copy_from_slice(&start_value);
338 let mut attrs: Vec<RawId> = Vec::new();
339 set.vae.infixes::<32, ID_LEN, _>(&v_prefix, |a: &[u8; ID_LEN]| {
340 if a == excluded {
341 return;
342 }
343 attrs.push(*a);
344 });
345 for attr in attrs {
346 let mut va_prefix = [0u8; 32 + ID_LEN];
347 va_prefix[..32].copy_from_slice(&start_value);
348 va_prefix[32..].copy_from_slice(&attr);
349 set.vae
350 .infixes::<{ 32 + ID_LEN }, ID_LEN, _>(&va_prefix, |entity: &[u8; ID_LEN]| {
351 results.insert(*entity);
352 });
353 }
354 results
355}
356
357fn eval_attr_inverse(set: &TribleSet, attr: &RawId, start: &RawId) -> HashSet<RawId> {
362 let mut results = HashSet::new();
363 let start_value = id_into_value(start);
364 let mut prefix = [0u8; 32 + ID_LEN];
365 prefix[..32].copy_from_slice(&start_value);
366 prefix[32..].copy_from_slice(attr);
367 set.vae
368 .infixes::<{ 32 + ID_LEN }, ID_LEN, _>(&prefix, |entity: &[u8; ID_LEN]| {
369 results.insert(*entity);
370 });
371 results
372}
373
374fn has_unbounded_closure(expr: &PathExpr) -> bool {
386 match expr {
387 PathExpr::Plus(_) | PathExpr::Star(_) => true,
388 PathExpr::NotAttr(_) | PathExpr::InverseNotAttr(_) => true,
389 PathExpr::Attr(_) | PathExpr::InverseAttr(_) => false,
390 PathExpr::Concat(a, b) | PathExpr::Union(a, b) => {
391 has_unbounded_closure(a) || has_unbounded_closure(b)
392 }
393 PathExpr::Optional(body) => has_unbounded_closure(body),
394 }
395}
396
397fn eval_from(set: &TribleSet, expr: &PathExpr, start: &RawId) -> HashSet<RawId> {
398 match expr {
399 PathExpr::Attr(attr) => eval_attr(set, attr, start),
400 PathExpr::InverseAttr(attr) => eval_attr_inverse(set, attr, start),
401 PathExpr::NotAttr(excluded) => eval_not_attr(set, excluded, start),
402 PathExpr::InverseNotAttr(excluded) => eval_not_attr_inverse(set, excluded, start),
403 PathExpr::Concat(lhs, rhs) => {
404 if has_unbounded_closure(lhs) || has_unbounded_closure(rhs) {
405 let mut results = HashSet::new();
410 for mid in eval_from(set, lhs, start) {
411 results.extend(eval_from(set, rhs, &mid));
412 }
413 return results;
414 }
415 let (constraint, dest_idx) = build_join(set, expr, start);
416 Query::new(constraint, move |binding: &Binding| {
417 let raw = binding.get(dest_idx)?;
418 id_from_value(raw)
419 })
420 .collect()
421 }
422 PathExpr::Union(lhs, rhs) => {
423 let mut results = eval_from(set, lhs, start);
424 results.extend(eval_from(set, rhs, start));
425 results
426 }
427 PathExpr::Plus(body) => {
428 let mut visited: HashSet<RawId> = HashSet::new();
429 let mut results: HashSet<RawId> = HashSet::new();
430 let mut frontier: VecDeque<RawId> = VecDeque::new();
431 frontier.push_back(*start);
432 visited.insert(*start);
433
434 while let Some(node) = frontier.pop_front() {
435 for dest in eval_from(set, body, &node) {
436 results.insert(dest);
437 if visited.insert(dest) {
438 frontier.push_back(dest);
439 }
440 }
441 }
442 results
443 }
444 PathExpr::Star(body) => {
445 let mut results = eval_from(set, &PathExpr::Plus(body.clone()), start);
446 results.insert(*start);
447 results
448 }
449 PathExpr::Optional(body) => {
450 let mut results = eval_from(set, body, start);
451 results.insert(*start);
452 results
453 }
454 }
455}
456
457fn has_path(set: &TribleSet, expr: &PathExpr, from: &RawId, to: &RawId) -> bool {
458 match expr {
459 PathExpr::Attr(attr) => eval_attr(set, attr, from).contains(to),
460 PathExpr::InverseAttr(attr) => eval_attr_inverse(set, attr, from).contains(to),
461 PathExpr::NotAttr(excluded) => eval_not_attr(set, excluded, from).contains(to),
462 PathExpr::InverseNotAttr(excluded) => eval_not_attr_inverse(set, excluded, from).contains(to),
463 PathExpr::Concat(lhs, rhs) if has_unbounded_closure(lhs) || has_unbounded_closure(rhs) => {
464 for mid in eval_from(set, lhs, from) {
466 if has_path(set, rhs, &mid, to) {
467 return true;
468 }
469 }
470 false
471 }
472 PathExpr::Concat(_, _) => {
473 let (constraint, dest_idx) = build_join(set, expr, from);
474 Query::new(constraint, move |binding: &Binding| {
475 let raw = binding.get(dest_idx)?;
476 id_from_value(raw)
477 })
478 .any(|dest| dest == *to)
479 }
480 PathExpr::Union(lhs, rhs) => has_path(set, lhs, from, to) || has_path(set, rhs, from, to),
481 PathExpr::Plus(body) => {
482 let mut visited: HashSet<RawId> = HashSet::new();
483 let mut frontier: VecDeque<RawId> = VecDeque::new();
484 frontier.push_back(*from);
485 visited.insert(*from);
486
487 while let Some(node) = frontier.pop_front() {
488 for dest in eval_from(set, body, &node) {
489 if dest == *to {
490 return true;
491 }
492 if visited.insert(dest) {
493 frontier.push_back(dest);
494 }
495 }
496 }
497 false
498 }
499 PathExpr::Star(body) => {
500 if from == to {
501 return true;
502 }
503 has_path(set, &PathExpr::Plus(body.clone()), from, to)
504 }
505 PathExpr::Optional(body) => {
506 if from == to {
507 return true;
508 }
509 has_path(set, body, from, to)
510 }
511 }
512}
513
514const RPQ_ESTIMATE_DEPTH: usize = 5;
520
521fn bounded_eval_from(
532 set: &TribleSet,
533 expr: &PathExpr,
534 start: &RawId,
535 depth: usize,
536) -> HashSet<RawId> {
537 match expr {
538 PathExpr::Attr(attr) => eval_attr(set, attr, start),
539 PathExpr::InverseAttr(attr) => eval_attr_inverse(set, attr, start),
540 PathExpr::NotAttr(excluded) => eval_not_attr(set, excluded, start),
541 PathExpr::InverseNotAttr(excluded) => eval_not_attr_inverse(set, excluded, start),
542 PathExpr::Concat(lhs, rhs) => {
543 let mut results = HashSet::new();
544 for mid in bounded_eval_from(set, lhs, start, depth) {
545 results.extend(bounded_eval_from(set, rhs, &mid, depth));
546 }
547 results
548 }
549 PathExpr::Union(lhs, rhs) => {
550 let mut results = bounded_eval_from(set, lhs, start, depth);
551 results.extend(bounded_eval_from(set, rhs, start, depth));
552 results
553 }
554 PathExpr::Plus(body) => {
555 let mut results: HashSet<RawId> = HashSet::new();
556 let mut visited: HashSet<RawId> = HashSet::new();
557 let mut frontier: Vec<RawId> = vec![*start];
558 visited.insert(*start);
559 for _ in 0..depth {
560 let mut next: Vec<RawId> = Vec::new();
561 for node in &frontier {
562 for dest in bounded_eval_from(set, body, node, depth) {
563 results.insert(dest);
564 if visited.insert(dest) {
565 next.push(dest);
566 }
567 }
568 }
569 if next.is_empty() {
570 break;
571 }
572 frontier = next;
573 }
574 results
575 }
576 PathExpr::Star(body) => {
577 let mut results = bounded_eval_from(
578 set,
579 &PathExpr::Plus(body.clone()),
580 start,
581 depth,
582 );
583 results.insert(*start);
584 results
585 }
586 PathExpr::Optional(body) => {
587 let mut results = bounded_eval_from(set, body, start, depth);
588 results.insert(*start);
589 results
590 }
591 }
592}
593
594fn estimate_from(set: &TribleSet, expr: &PathExpr, start: &RawId) -> usize {
597 let body = match expr {
599 PathExpr::Star(inner) | PathExpr::Plus(inner) | PathExpr::Optional(inner) => {
600 inner.as_ref()
601 }
602 other => other,
603 };
604 match body {
605 PathExpr::Attr(attr) => {
606 let mut prefix = [0u8; ID_LEN * 2];
607 prefix[..ID_LEN].copy_from_slice(start);
608 prefix[ID_LEN..].copy_from_slice(attr);
609 set.eav.segmented_len(&prefix) as usize
610 }
611 PathExpr::InverseAttr(attr) => {
612 let start_value = id_into_value(start);
613 let mut prefix = [0u8; 32 + ID_LEN];
614 prefix[..32].copy_from_slice(&start_value);
615 prefix[32..].copy_from_slice(attr);
616 set.vae.segmented_len(&prefix) as usize
617 }
618 PathExpr::Union(lhs, rhs) => {
619 estimate_from(set, lhs, start) + estimate_from(set, rhs, start)
620 }
621 _ if has_unbounded_closure(body) => {
632 bounded_eval_from(set, body, start, RPQ_ESTIMATE_DEPTH).len()
633 }
634 _ => {
635 let (constraint, dest_idx) = build_join(set, body, start);
636 let mut binding = Binding::default();
637 let start_inline: Inline<GenId> = start.to_inline();
638 binding.set(0, &start_inline.raw);
639 constraint.estimate(dest_idx, &binding).unwrap_or(0)
640 }
641 }
642}
643
644pub struct RegularPathConstraint {
657 start: VariableId,
658 end: VariableId,
659 expr: PathExpr,
660 inverse_expr: PathExpr,
667 set: TribleSet,
668}
669
670impl RegularPathConstraint {
671 pub fn new(
674 set: TribleSet,
675 start: Variable<GenId>,
676 end: Variable<GenId>,
677 ops: &[PathOp],
678 ) -> Self {
679 let expr = PathExpr::from_postfix(ops);
680 let inverse_expr = invert(expr.clone());
681 RegularPathConstraint {
682 start: start.index,
683 end: end.index,
684 expr,
685 inverse_expr,
686 set,
687 }
688 }
689
690 fn all_nodes(&self) -> Vec<RawInline> {
693 let mut node_set: HashSet<RawInline> = HashSet::new();
694 for t in self.set.iter() {
695 let v = &t.data[32..64];
696 if v[..ID_LEN] == [0; ID_LEN] {
697 let dest: RawId = v[ID_LEN..].try_into().unwrap();
698 node_set.insert(id_into_value(&dest));
699 let e: RawId = t.data[..ID_LEN].try_into().unwrap();
700 node_set.insert(id_into_value(&e));
701 }
702 }
703 node_set.into_iter().collect()
704 }
705}
706
707impl<'a> Constraint<'a> for RegularPathConstraint {
708 fn variables(&self) -> VariableSet {
709 let mut vars = VariableSet::new_empty();
710 vars.set(self.start);
711 vars.set(self.end);
712 vars
713 }
714
715 fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
716 if self.start == self.end && variable == self.start {
720 return Some(self.set.len());
721 }
722 if variable == self.end {
723 if let Some(start_val) = binding.get(self.start) {
724 if let Some(start_id) = id_from_value(start_val) {
725 return Some(estimate_from(&self.set, &self.expr, &start_id).max(1));
726 }
727 return Some(0);
728 }
729 Some(self.set.len())
730 } else if variable == self.start {
731 if let Some(end_val) = binding.get(self.end) {
732 if let Some(end_id) = id_from_value(end_val) {
733 return Some(estimate_from(&self.set, &self.inverse_expr, &end_id).max(1));
738 }
739 return Some(0);
740 }
741 Some(self.set.len())
742 } else {
743 None
744 }
745 }
746
747 fn propose(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawInline>) {
748 if self.start == self.end && variable == self.start {
753 let candidates = self.all_nodes();
754 proposals.extend(candidates.into_iter().filter(|v| {
755 id_from_value(v)
756 .map_or(false, |id| has_path(&self.set, &self.expr, &id, &id))
757 }));
758 return;
759 }
760 if variable == self.end {
761 if let Some(start_val) = binding.get(self.start) {
762 if let Some(start_id) = id_from_value(start_val) {
763 let reachable = eval_from(&self.set, &self.expr, &start_id);
764 proposals.extend(reachable.iter().map(id_into_value));
765 }
766 return;
767 }
768 }
769 if variable == self.start {
770 if let Some(end_val) = binding.get(self.end) {
771 if let Some(end_id) = id_from_value(end_val) {
780 let reachable = eval_from(&self.set, &self.inverse_expr, &end_id);
781 proposals.extend(reachable.iter().map(id_into_value));
782 }
783 return;
784 }
785 }
786 if variable == self.start || variable == self.end {
787 proposals.extend(self.all_nodes());
788 }
789 }
790
791 fn confirm(&self, variable: VariableId, binding: &Binding, proposals: &mut Vec<RawInline>) {
792 if self.start == self.end && variable == self.start {
795 proposals.retain(|v| {
796 id_from_value(v)
797 .map_or(false, |id| has_path(&self.set, &self.expr, &id, &id))
798 });
799 return;
800 }
801 if variable == self.start {
802 if let Some(end_val) = binding.get(self.end) {
803 if let Some(end_id) = id_from_value(end_val) {
804 proposals.retain(|v| {
805 id_from_value(v)
806 .map_or(false, |sid| has_path(&self.set, &self.expr, &sid, &end_id))
807 });
808 } else {
809 proposals.clear();
810 }
811 }
812 } else if variable == self.end {
813 if let Some(start_val) = binding.get(self.start) {
814 if let Some(start_id) = id_from_value(start_val) {
815 proposals.retain(|v| {
816 id_from_value(v).map_or(false, |eid| {
817 has_path(&self.set, &self.expr, &start_id, &eid)
818 })
819 });
820 } else {
821 proposals.clear();
822 }
823 }
824 }
825 }
826}