1use crate::{Direction, GraphStore, GraphStoreError, GraphStoreResult, PersistentGraphStore};
11use std::collections::{BTreeMap, BTreeSet};
12use std::sync::Arc;
13use uqa_storage::{CatalogFacade, PersistentStorageBackend};
14
15type Pairs = BTreeSet<(u64, u64)>;
16type MemoryPaths = BTreeMap<Vec<String>, Pairs>;
17
18#[derive(Clone)]
19enum PathStorage {
20 Memory(Arc<MemoryPaths>),
21 Persistent(Arc<DurablePathIndex>),
22 ReadView {
23 store: Arc<crate::GraphStoreHandle>,
24 graph: String,
25 sequences: Vec<Vec<String>>,
26 },
27}
28
29struct DurablePathIndex {
30 catalog: Arc<dyn CatalogFacade>,
31 backend: Arc<dyn PersistentStorageBackend>,
32 key: String,
33 graph: String,
34 sequences: Vec<Vec<String>>,
35 definition: String,
36 read_gate: parking_lot::ReentrantMutex<()>,
37}
38
39#[derive(Clone)]
42pub struct PathIndex {
43 storage: PathStorage,
44}
45
46impl std::fmt::Debug for PathIndex {
47 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 formatter
49 .debug_struct("PathIndex")
50 .field("paths", &self.indexed_paths())
51 .field(
52 "persistent",
53 &matches!(self.storage, PathStorage::Persistent(_)),
54 )
55 .finish()
56 }
57}
58
59impl Default for PathIndex {
60 fn default() -> Self {
61 Self {
62 storage: PathStorage::Memory(Arc::default()),
63 }
64 }
65}
66
67fn json_error(error: &serde_json::Error) -> GraphStoreError {
68 GraphStoreError::CorruptGraph(error.to_string())
69}
70
71impl PathIndex {
72 pub fn build<G: GraphStore>(
73 store: &G,
74 graph: &str,
75 sequences: &[Vec<String>],
76 ) -> GraphStoreResult<Self> {
77 let mut paths = BTreeMap::new();
78 for sequence in sequences {
79 let mut pairs = Pairs::new();
80 visit_pairs(store, graph, sequence, |pair| {
81 pairs.insert(pair);
82 Ok(())
83 })?;
84 paths.insert(sequence.clone(), pairs);
85 }
86 Ok(Self {
87 storage: PathStorage::Memory(Arc::new(paths)),
88 })
89 }
90
91 pub fn open_persistent(
93 catalog: Arc<dyn CatalogFacade>,
94 backend: Arc<dyn PersistentStorageBackend>,
95 key: &str,
96 graph: &str,
97 sequences: &[Vec<String>],
98 ) -> GraphStoreResult<Self> {
99 Ok(Self {
100 storage: PathStorage::Persistent(Arc::new(DurablePathIndex {
101 catalog,
102 backend,
103 key: key.to_owned(),
104 graph: graph.to_owned(),
105 sequences: sequences.to_vec(),
106 definition: serde_json::to_string(sequences).map_err(|error| json_error(&error))?,
107 read_gate: parking_lot::ReentrantMutex::new(()),
108 })),
109 })
110 }
111
112 pub fn build_persistent(
115 catalog: Arc<dyn CatalogFacade>,
116 backend: Arc<dyn PersistentStorageBackend>,
117 key: &str,
118 graph: &str,
119 sequences: &[Vec<String>],
120 ) -> GraphStoreResult<Self> {
121 let definition = serde_json::to_string(sequences).map_err(|error| json_error(&error))?;
122 let mut store =
123 PersistentGraphStore::from_catalog(Arc::clone(&catalog), Arc::clone(&backend));
124 store.transaction(|store| {
125 catalog.save_path_index(key, &definition)?;
126 catalog.clear_path_index_data(key)?;
127 for sequence in sequences {
128 let sequence_key =
129 serde_json::to_string(sequence).map_err(|error| json_error(&error))?;
130 let mut page = Vec::with_capacity(256);
131 visit_pairs(store, graph, sequence, |pair| {
132 page.push(pair);
133 if page.len() == 256 {
134 catalog.save_path_index_pairs(key, &sequence_key, &page)?;
135 page.clear();
136 }
137 Ok(())
138 })?;
139 if !page.is_empty() {
140 catalog.save_path_index_pairs(key, &sequence_key, &page)?;
141 }
142 }
143 catalog.finish_path_index_data(key, graph, &definition)?;
144 Ok(())
145 })?;
146 Self::open_persistent(catalog, backend, key, graph, sequences)
147 }
148
149 pub fn rebind_persistent(
151 &self,
152 catalog: Arc<dyn CatalogFacade>,
153 backend: Arc<dyn PersistentStorageBackend>,
154 ) -> GraphStoreResult<Self> {
155 match &self.storage {
156 PathStorage::Memory(_) | PathStorage::ReadView { .. } => Ok(self.clone()),
157 PathStorage::Persistent(index) => {
158 Self::open_persistent(catalog, backend, &index.key, &index.graph, &index.sequences)
159 }
160 }
161 }
162
163 #[must_use]
167 pub fn with_graph_read_view(&self, store: Arc<crate::GraphStoreHandle>) -> Self {
168 let (graph, sequences) = match &self.storage {
169 PathStorage::Memory(_) => return self.clone(),
170 PathStorage::Persistent(index) => (&index.graph, &index.sequences),
171 PathStorage::ReadView {
172 graph, sequences, ..
173 } => (graph, sequences),
174 };
175 Self {
176 storage: PathStorage::ReadView {
177 store,
178 graph: graph.clone(),
179 sequences: sequences.clone(),
180 },
181 }
182 }
183
184 pub fn lookup(&self, sequence: &[String]) -> GraphStoreResult<Option<Pairs>> {
185 match &self.storage {
186 PathStorage::Memory(paths) => Ok(paths.get(sequence).cloned()),
187 PathStorage::ReadView {
188 store,
189 graph,
190 sequences,
191 } => {
192 if !sequences.iter().any(|candidate| candidate == sequence) {
193 return Ok(None);
194 }
195 let mut pairs = Pairs::new();
196 visit_pairs(store.as_ref(), graph, sequence, |pair| {
197 pairs.insert(pair);
198 Ok(())
199 })?;
200 Ok(Some(pairs))
201 }
202 PathStorage::Persistent(index) => {
203 if !index
204 .sequences
205 .iter()
206 .any(|candidate| candidate == sequence)
207 {
208 return Ok(None);
209 }
210 index.lookup(sequence).map(Some)
211 }
212 }
213 }
214
215 pub fn has_path(&self, sequence: &[String]) -> bool {
216 match &self.storage {
217 PathStorage::Memory(paths) => paths.contains_key(sequence),
218 PathStorage::ReadView { sequences, .. } => {
219 sequences.iter().any(|candidate| candidate == sequence)
220 }
221 PathStorage::Persistent(index) => index
222 .sequences
223 .iter()
224 .any(|candidate| candidate == sequence),
225 }
226 }
227
228 pub fn indexed_paths(&self) -> Vec<String> {
229 let paths: BTreeSet<String> = match &self.storage {
230 PathStorage::Memory(paths) => paths.keys().map(|sequence| sequence.join("/")).collect(),
231 PathStorage::ReadView { sequences, .. } => sequences
232 .iter()
233 .map(|sequence| sequence.join("/"))
234 .collect(),
235 PathStorage::Persistent(index) => index
236 .sequences
237 .iter()
238 .map(|sequence| sequence.join("/"))
239 .collect(),
240 };
241 paths.into_iter().collect()
242 }
243}
244
245impl DurablePathIndex {
246 fn lookup(&self, sequence: &[String]) -> GraphStoreResult<Pairs> {
247 struct ReadCheckpoint(Option<Arc<dyn PersistentStorageBackend>>);
248 impl Drop for ReadCheckpoint {
249 fn drop(&mut self) {
250 if let Some(backend) = &self.0 {
251 let _ = backend.rollback_transaction();
252 }
253 }
254 }
255 let _read = self.read_gate.lock();
256 if self.backend.in_transaction() {
257 return self.lookup_in_snapshot(
258 Arc::clone(&self.catalog),
259 Arc::clone(&self.backend),
260 sequence,
261 );
262 }
263 let session = if self.backend.supports_concurrent_pinned_read_and_write() {
266 self.backend.open_session()?
267 } else {
268 uqa_storage::PersistentStorageSession::new(
271 Arc::clone(&self.catalog),
272 Arc::clone(&self.backend),
273 )
274 };
275 let catalog = session.catalog;
276 let backend = session.backend;
277 backend.begin_read_transaction()?;
278 let mut checkpoint = ReadCheckpoint(Some(Arc::clone(&backend)));
279 let result = self.lookup_in_snapshot(catalog, Arc::clone(&backend), sequence);
280 backend.rollback_transaction()?;
281 checkpoint.0 = None;
282 result
283 }
284
285 fn lookup_in_snapshot(
286 &self,
287 catalog: Arc<dyn CatalogFacade>,
288 backend: Arc<dyn PersistentStorageBackend>,
289 sequence: &[String],
290 ) -> GraphStoreResult<Pairs> {
291 let definition = catalog
292 .load_path_indexes()?
293 .into_iter()
294 .find_map(|(key, json)| (key == self.key).then_some(json));
295 if definition.as_deref() != Some(self.definition.as_str()) {
296 return Err(GraphStoreError::InvalidQuery(format!(
297 "path index {:?} was dropped or redefined",
298 self.key
299 )));
300 }
301 let mut result = Pairs::new();
302 if catalog.path_index_data_is_current(&self.key, &self.definition)? {
303 let sequence_key =
304 serde_json::to_string(sequence).map_err(|error| json_error(&error))?;
305 let mut after = None;
306 loop {
307 let pairs = catalog.path_index_pairs(&self.key, &sequence_key, after, 256)?;
308 if pairs.is_empty() {
309 break;
310 }
311 after = pairs.last().copied();
312 result.extend(pairs);
313 }
314 } else {
315 let store = PersistentGraphStore::from_catalog(catalog, backend);
319 visit_pairs(&store, &self.graph, sequence, |pair| {
320 result.insert(pair);
321 Ok(())
322 })?;
323 }
324 Ok(result)
325 }
326}
327
328fn visit_pairs<G: GraphStore>(
329 store: &G,
330 graph: &str,
331 sequence: &[String],
332 mut visit: impl FnMut((u64, u64)) -> GraphStoreResult<()>,
333) -> GraphStoreResult<()> {
334 let mut after = None;
335 loop {
336 let ids = store.vertex_id_page(graph, after, 256)?;
337 if ids.is_empty() {
338 break;
339 }
340 after = ids.last().copied();
341 for start in ids {
342 let mut frontier = BTreeSet::from([start]);
343 for label in sequence {
344 let mut next = BTreeSet::new();
345 for vertex in frontier {
346 next.extend(store.neighbors(vertex, Some(label), Direction::Out, graph)?);
347 }
348 frontier = next;
349 if frontier.is_empty() {
350 break;
351 }
352 }
353 for end in frontier {
354 visit((start, end))?;
355 }
356 }
357 }
358 Ok(())
359}