uqa_graph/operators/
regular_path.rs1use super::{
10 build_nfa, simplify, subset_construction, BTreeMap, BTreeSet, Dfa, DfaState, DocId,
11 GraphPayload, GraphPostingList, GraphStore, GraphStoreError, GraphStoreResult, Payload,
12 PostingEntry, PostingList, RegularPathExpr, VecDeque, VertexId, DEFAULT_GRAPH_SCORE,
13};
14
15pub struct RegularPathQuery<'a> {
24 pub path: RegularPathExpr,
25 pub graph: &'a str,
26 pub start_vertex: Option<VertexId>,
29 pub score: f64,
30}
31
32impl<'a> RegularPathQuery<'a> {
33 pub fn new(path: RegularPathExpr, graph: &'a str) -> Self {
34 Self {
35 path,
36 graph,
37 start_vertex: None,
38 score: DEFAULT_GRAPH_SCORE,
39 }
40 }
41
42 pub fn from_vertex(mut self, start: VertexId) -> Self {
43 self.start_vertex = Some(start);
44 self
45 }
46
47 pub fn execute<G: GraphStore>(&self, store: &G) -> GraphStoreResult<GraphPostingList> {
48 let simplified = simplify(&self.path)
49 .map_err(|error| GraphStoreError::InvalidQuery(error.to_string()))?;
50 let nfa = build_nfa(&simplified)
51 .map_err(|error| GraphStoreError::InvalidQuery(error.to_string()))?;
52 let dfa = subset_construction(&nfa)
53 .map_err(|error| GraphStoreError::InvalidQuery(error.to_string()))?;
54
55 let starts: Vec<VertexId> = match self.start_vertex {
56 Some(v) => {
57 store.require_vertex_in_graph(v, self.graph)?;
58 vec![v]
59 }
60 None => store.vertex_ids_in_graph(self.graph)?.into_iter().collect(),
61 };
62
63 let mut pairs: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
64 for sv in &starts {
65 self.simulate_from(store, *sv, &dfa, &mut pairs)?;
66 }
67
68 let mut entries: Vec<PostingEntry> = Vec::new();
69 let mut graph_payloads: BTreeMap<DocId, GraphPayload> = BTreeMap::new();
70 let mut seen: BTreeSet<DocId> = BTreeSet::new();
71 for (start_v, end_v) in &pairs {
72 let doc_id = *end_v;
73 if seen.insert(doc_id) {
74 entries.push(PostingEntry::new(doc_id, Payload::with_score(self.score)));
75 let mut subgraph_vertices = vec![*start_v, *end_v];
76 subgraph_vertices.sort_unstable();
77 subgraph_vertices.dedup();
78 graph_payloads.insert(
79 doc_id,
80 GraphPayload {
81 subgraph_vertices,
82 subgraph_edges: Vec::new(),
83 graph_name: self.graph.to_string(),
84 score_override: Some(self.score),
85 },
86 );
87 }
88 }
89 entries.sort_by_key(|e| e.doc_id);
90 GraphPostingList::try_from_parts(
91 PostingList::from_sorted_unchecked(entries),
92 graph_payloads,
93 )
94 .map_err(Into::into)
95 }
96
97 fn simulate_from<G: GraphStore>(
98 &self,
99 store: &G,
100 start: VertexId,
101 dfa: &Dfa,
102 pairs: &mut BTreeSet<(VertexId, VertexId)>,
103 ) -> GraphStoreResult<()> {
104 let mut visited: BTreeSet<(VertexId, DfaState)> = BTreeSet::new();
105 let mut queue: VecDeque<(VertexId, DfaState)> = VecDeque::new();
106 queue.push_back((start, dfa.start.clone()));
107 visited.insert((start, dfa.start.clone()));
108
109 if dfa.accepts.contains(&dfa.start) {
110 pairs.insert((start, start));
111 }
112
113 while let Some((vertex, state)) = queue.pop_front() {
114 let Some(transitions) = dfa.transitions.get(&state) else {
115 continue;
116 };
117 for eid in store.out_edge_ids(vertex, self.graph)? {
118 let edge = store.get_edge(eid).ok_or_else(|| {
119 GraphStoreError::CorruptGraph(format!("missing RPQ edge {eid}"))
120 })?;
121 let Some(next_state) = transitions.get(&edge.label) else {
122 continue;
123 };
124 let neighbor = edge.target_id;
125 if dfa.accepts.contains(next_state) {
126 pairs.insert((start, neighbor));
127 }
128 let key = (neighbor, next_state.clone());
129 if visited.insert(key.clone()) {
130 queue.push_back(key);
131 }
132 }
133 }
134 Ok(())
135 }
136}