uqa_graph/
operator_impls.rs1use std::sync::Arc;
15
16use uqa_core::{IndexStats, PostingList};
17use uqa_operators::base::{ExecutionContext, Operator, OperatorResult};
18use uqa_operators::PathWeightPredicate;
19use uqa_storage::StorageBackendError;
20
21use crate::cypher::{CypherQuery, CypherWriter};
22use crate::memory_store::MemoryGraphStore;
23use crate::operators::{WeightedPathQuery, DEFAULT_GRAPH_SCORE};
24use crate::rpq::RegularPathExpr;
25
26pub struct WeightedPathQueryOperator {
31 pub path_expr: RegularPathExpr,
32 pub graph_store: Arc<MemoryGraphStore>,
33 pub graph_name: String,
34 pub start_vertex: Option<u64>,
35 pub weight_property: String,
36 pub default_edge_weight: f64,
37 pub max_hops: usize,
38 pub predicate: PathWeightPredicate,
39 pub predicate_selectivity: f64,
40 pub score: f64,
41}
42
43impl WeightedPathQueryOperator {
44 #[must_use]
45 pub fn new(
46 path_expr: RegularPathExpr,
47 graph_store: Arc<MemoryGraphStore>,
48 graph_name: impl Into<String>,
49 ) -> Self {
50 Self {
51 path_expr,
52 graph_store,
53 graph_name: graph_name.into(),
54 start_vertex: None,
55 weight_property: "weight".to_string(),
56 default_edge_weight: 1.0,
57 max_hops: 16,
58 predicate: Arc::new(|_| true),
59 predicate_selectivity: 1.0,
60 score: DEFAULT_GRAPH_SCORE,
61 }
62 }
63
64 #[must_use]
65 pub fn from_vertex(mut self, start: u64) -> Self {
66 self.start_vertex = Some(start);
67 self
68 }
69
70 #[must_use]
71 pub fn with_predicate_selectivity(mut self, sel: f64) -> Self {
72 self.predicate_selectivity = sel;
73 self
74 }
75
76 #[must_use]
77 pub fn with_predicate(
78 mut self,
79 predicate: impl Fn(f64) -> bool + Send + Sync + 'static,
80 selectivity: f64,
81 ) -> Self {
82 self.predicate = Arc::new(predicate);
83 self.predicate_selectivity = selectivity;
84 self
85 }
86
87 #[must_use]
88 pub fn with_weight_property(mut self, property: impl Into<String>) -> Self {
89 self.weight_property = property.into();
90 self
91 }
92
93 #[must_use]
94 pub fn with_default_edge_weight(mut self, weight: f64) -> Self {
95 self.default_edge_weight = weight;
96 self
97 }
98
99 #[must_use]
100 pub fn with_max_hops(mut self, max_hops: usize) -> Self {
101 self.max_hops = max_hops;
102 self
103 }
104
105 #[must_use]
106 pub fn with_score(mut self, score: f64) -> Self {
107 self.score = score;
108 self
109 }
110}
111
112impl Operator for WeightedPathQueryOperator {
113 fn execute(&self, _ctx: &ExecutionContext) -> OperatorResult {
114 let mut query = WeightedPathQuery::new(
115 self.path_expr.clone(),
116 &self.graph_name,
117 &self.weight_property,
118 Arc::clone(&self.predicate),
119 );
120 query.default_edge_weight = self.default_edge_weight;
121 query.max_hops = self.max_hops;
122 query.score = self.score;
123 if let Some(v) = self.start_vertex {
124 query = query.from_vertex(v);
125 }
126 Ok(query
127 .execute(self.graph_store.as_ref())
128 .map_err(|error| StorageBackendError::Other(error.to_string()))?
129 .to_posting_list())
130 }
131
132 fn cost_estimate(&self, stats: &IndexStats) -> f64 {
133 let n = stats.total_docs as f64;
137 n * n
138 }
139}
140
141pub struct CypherQueryOperator {
151 pub graph_store: Arc<parking_lot::RwLock<MemoryGraphStore>>,
152 pub query: CypherQuery,
153 pub graph_name: String,
154 pub params: std::collections::BTreeMap<String, uqa_core::Value>,
155}
156
157impl CypherQueryOperator {
158 #[must_use]
159 pub fn new(
160 graph_store: Arc<parking_lot::RwLock<MemoryGraphStore>>,
161 query: CypherQuery,
162 graph_name: impl Into<String>,
163 ) -> Self {
164 Self {
165 graph_store,
166 query,
167 graph_name: graph_name.into(),
168 params: std::collections::BTreeMap::new(),
169 }
170 }
171
172 #[must_use]
173 pub fn with_params(
174 mut self,
175 params: std::collections::BTreeMap<String, uqa_core::Value>,
176 ) -> Self {
177 self.params = params;
178 self
179 }
180}
181
182impl Operator for CypherQueryOperator {
183 fn execute(&self, _ctx: &ExecutionContext) -> OperatorResult {
184 let mut guard = self.graph_store.write();
188 let mut writer = CypherWriter::new(&mut *guard, self.graph_name.clone())
189 .with_params(self.params.clone());
190 let (_cols, rows) = writer
191 .execute(&self.query)
192 .map_err(|error| StorageBackendError::Other(error.to_string()))?;
193 let mut ids: std::collections::BTreeSet<u64> = std::collections::BTreeSet::new();
198 for row in rows {
199 for value in row.values() {
200 if let uqa_core::Value::Int(n) = value {
201 if *n >= 0 {
202 ids.insert(*n as u64);
203 }
204 }
205 }
206 }
207 let entries: Vec<uqa_core::PostingEntry> = ids
208 .into_iter()
209 .map(|id| uqa_core::PostingEntry::new(id, uqa_core::Payload::default()))
210 .collect();
211 Ok(PostingList::from_sorted_unchecked(entries))
212 }
213
214 fn cost_estimate(&self, stats: &IndexStats) -> f64 {
215 stats.total_docs as f64
216 }
217}