drasi_core/path_solver/
solution.rs

1// Copyright 2024 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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                    //log warning
148                }
149            }
150            slot_num += 1;
151        }
152        result
153    }
154}