drasi_core/path_solver/
solution.rs1use hashers::jenkins::spooky_hash::SpookyHasher;
16
17use crate::evaluation::context::QueryVariables;
18use crate::evaluation::variable_value::VariableValue;
19
20use std::hash::{Hash, Hasher};
21
22use std::collections::{HashSet, VecDeque};
23
24use crate::models::Element;
25
26use std::sync::Arc;
27
28use std::collections::BTreeMap;
29
30use super::match_path::MatchPath;
31
32pub(crate) type SolutionSignature = u64;
33
34#[derive(Clone, Debug)]
35pub struct MatchPathSolution {
36 pub(crate) solved_slots: BTreeMap<usize, Option<Arc<Element>>>,
37 pub(crate) total_slots: usize,
38 pub(crate) queued_slots: Vec<bool>,
39 pub(crate) slot_cursors: VecDeque<(usize, Option<Arc<Element>>)>,
40 pub(crate) solution_signature: Option<SolutionSignature>,
41 pub(crate) anchor_slot: usize,
42}
43
44impl MatchPathSolution {
45 pub fn new(total_slots: usize, anchor_slot: usize) -> Self {
46 let mut queued_slots = Vec::new();
47 queued_slots.resize(total_slots, false);
48
49 MatchPathSolution {
50 solved_slots: BTreeMap::new(),
51 total_slots,
52 queued_slots,
53 slot_cursors: VecDeque::new(),
54 solution_signature: None,
55 anchor_slot,
56 }
57 }
58
59 pub fn mark_slot_solved(&mut self, slot_num: usize, value: Option<Arc<Element>>) {
60 self.solved_slots.insert(slot_num, value);
61
62 if self.solved_slots.len() == self.total_slots {
63 let mut hasher = SpookyHasher::default();
64 for (slot_num, value) in &self.solved_slots {
65 slot_num.hash(&mut hasher);
66 match value {
67 Some(value) => {
68 let elem_ref = value.get_reference();
69 elem_ref.source_id.hash(&mut hasher);
70 elem_ref.element_id.hash(&mut hasher);
71 }
72 None => 0.hash(&mut hasher),
73 }
74 }
75 self.solution_signature = Some(hasher.finish());
76 }
77 }
78
79 pub fn enqueue_slot(&mut self, slot_num: usize, value: Option<Arc<Element>>) {
80 if !self.queued_slots[slot_num] {
81 self.slot_cursors.push_back((slot_num, value));
82 self.queued_slots[slot_num] = true;
83 }
84 }
85
86 pub fn is_slot_solved(&self, slot_num: usize) -> bool {
87 self.solved_slots.contains_key(&slot_num)
88 }
89
90 pub fn get_solution_signature(&self) -> Option<SolutionSignature> {
91 self.solution_signature
92 }
93
94 pub fn get_empty_optional_solution(&self, match_path: &MatchPath) -> Option<MatchPathSolution> {
95 if !match_path.slots[self.anchor_slot].optional {
96 return None;
97 }
98
99 if self.solved_slots.len() != self.total_slots {
100 return None;
101 }
102
103 let empty_slots = self
104 .solved_slots
105 .iter()
106 .filter(|(_, value)| value.is_none())
107 .map(|(slot_num, _)| *slot_num)
108 .collect::<HashSet<_>>();
109
110 let opt_slots =
111 match_path.get_optional_slots_on_common_paths(self.anchor_slot, empty_slots);
112
113 let mut result = self.clone();
114 for slot_num in &opt_slots {
115 result.solved_slots.remove(slot_num);
116 }
117 result.solution_signature = None;
118 for slot_num in &opt_slots {
119 result.mark_slot_solved(*slot_num, None);
120 }
121
122 Some(result)
123 }
124
125 #[allow(clippy::explicit_counter_loop)]
126 pub fn into_query_variables(
127 &self,
128 match_path: &MatchPath,
129 base_variables: &QueryVariables,
130 ) -> QueryVariables {
131 let mut result = base_variables.clone();
132 let mut slot_num = 0;
133 for slot in &match_path.slots {
134 match self.solved_slots.get(&slot_num) {
135 Some(element) => {
136 if let Some(annotation) = &slot.spec.annotation {
137 result.insert(
138 annotation.to_string().into_boxed_str(),
139 match element {
140 Some(element) => element.to_expression_variable(),
141 None => VariableValue::Null,
142 },
143 );
144 }
145 }
146 None => {
147 }
149 }
150 slot_num += 1;
151 }
152 result
153 }
154}