1use selene_core::{CancellationChecker, DbString, JsonPathSelector, JsonValue, NodeId, Value};
4
5use crate::error::GraphResult;
6use crate::graph::SeleneGraph;
7use crate::json_search::{
8 JSON_SEARCH_CANCEL_STRIDE, JsonContainmentHit, JsonPathContainmentHit, JsonPathHit,
9 JsonPathValueHit, JsonSearchError,
10};
11use crate::shared::SharedGraph;
12
13#[derive(Clone, Copy, Debug)]
15pub struct JsonPathContainmentCandidateOptions<'a> {
16 pub path: &'a [JsonPathSelector],
18 pub candidate: &'a JsonValue,
20 pub candidates: &'a [NodeId],
22 pub k: usize,
24}
25
26impl<'a> JsonPathContainmentCandidateOptions<'a> {
27 #[must_use]
29 pub const fn new(
30 path: &'a [JsonPathSelector],
31 candidate: &'a JsonValue,
32 candidates: &'a [NodeId],
33 k: usize,
34 ) -> Self {
35 Self {
36 path,
37 candidate,
38 candidates,
39 k,
40 }
41 }
42}
43
44impl SeleneGraph {
45 pub fn exact_json_contains_candidate_nodes(
47 &self,
48 label: &DbString,
49 property: &DbString,
50 candidate: &JsonValue,
51 candidates: &[NodeId],
52 k: usize,
53 ) -> GraphResult<Vec<JsonContainmentHit>> {
54 self.exact_json_contains_candidate_nodes_checked(
55 label,
56 property,
57 candidate,
58 candidates,
59 k,
60 CancellationChecker::disabled(),
61 )
62 .map_err(JsonSearchError::into_graph_error)
63 }
64
65 pub fn exact_json_contains_candidate_nodes_checked(
67 &self,
68 label: &DbString,
69 property: &DbString,
70 candidate: &JsonValue,
71 candidates: &[NodeId],
72 k: usize,
73 checker: CancellationChecker<'_>,
74 ) -> Result<Vec<JsonContainmentHit>, JsonSearchError> {
75 self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
76 json.contains(candidate).then_some(None)
77 })
78 .map(|hits| {
79 hits.into_iter()
80 .map(|(node_id, _)| JsonContainmentHit { node_id })
81 .collect()
82 })
83 }
84
85 pub fn exact_json_path_exists_candidate_nodes(
87 &self,
88 label: &DbString,
89 property: &DbString,
90 path: &[JsonPathSelector],
91 candidates: &[NodeId],
92 k: usize,
93 ) -> GraphResult<Vec<JsonPathHit>> {
94 self.exact_json_path_exists_candidate_nodes_checked(
95 label,
96 property,
97 path,
98 candidates,
99 k,
100 CancellationChecker::disabled(),
101 )
102 .map_err(JsonSearchError::into_graph_error)
103 }
104
105 pub fn exact_json_path_exists_candidate_nodes_checked(
107 &self,
108 label: &DbString,
109 property: &DbString,
110 path: &[JsonPathSelector],
111 candidates: &[NodeId],
112 k: usize,
113 checker: CancellationChecker<'_>,
114 ) -> Result<Vec<JsonPathHit>, JsonSearchError> {
115 if path.is_empty() {
116 return Ok(Vec::new());
117 }
118 self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
119 json.path_exists(path).then_some(None)
120 })
121 .map(|hits| {
122 hits.into_iter()
123 .map(|(node_id, _)| JsonPathHit { node_id })
124 .collect()
125 })
126 }
127
128 pub fn exact_json_path_contains_candidate_nodes(
130 &self,
131 label: &DbString,
132 property: &DbString,
133 options: JsonPathContainmentCandidateOptions<'_>,
134 ) -> GraphResult<Vec<JsonPathContainmentHit>> {
135 self.exact_json_path_contains_candidate_nodes_checked(
136 label,
137 property,
138 options,
139 CancellationChecker::disabled(),
140 )
141 .map_err(JsonSearchError::into_graph_error)
142 }
143
144 pub fn exact_json_path_contains_candidate_nodes_checked(
146 &self,
147 label: &DbString,
148 property: &DbString,
149 options: JsonPathContainmentCandidateOptions<'_>,
150 checker: CancellationChecker<'_>,
151 ) -> Result<Vec<JsonPathContainmentHit>, JsonSearchError> {
152 if options.path.is_empty() {
153 return Ok(Vec::new());
154 }
155 self.filter_json_candidate_nodes(
156 label,
157 property,
158 options.candidates,
159 options.k,
160 checker,
161 |json| {
162 json.path_contains(options.path, options.candidate)
163 .then_some(None)
164 },
165 )
166 .map(|hits| {
167 hits.into_iter()
168 .map(|(node_id, _)| JsonPathContainmentHit { node_id })
169 .collect()
170 })
171 }
172
173 pub fn exact_json_path_value_candidate_nodes(
175 &self,
176 label: &DbString,
177 property: &DbString,
178 path: &[JsonPathSelector],
179 candidates: &[NodeId],
180 k: usize,
181 ) -> GraphResult<Vec<JsonPathValueHit>> {
182 self.exact_json_path_value_candidate_nodes_checked(
183 label,
184 property,
185 path,
186 candidates,
187 k,
188 CancellationChecker::disabled(),
189 )
190 .map_err(JsonSearchError::into_graph_error)
191 }
192
193 pub fn exact_json_path_value_candidate_nodes_checked(
195 &self,
196 label: &DbString,
197 property: &DbString,
198 path: &[JsonPathSelector],
199 candidates: &[NodeId],
200 k: usize,
201 checker: CancellationChecker<'_>,
202 ) -> Result<Vec<JsonPathValueHit>, JsonSearchError> {
203 if path.is_empty() {
204 return Ok(Vec::new());
205 }
206 self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
207 json.path_value(path).map(Some)
208 })
209 .map(|hits| {
210 hits.into_iter()
211 .filter_map(|(node_id, value)| {
212 value.map(|value| JsonPathValueHit { node_id, value })
213 })
214 .collect()
215 })
216 }
217
218 fn filter_json_candidate_nodes(
219 &self,
220 label: &DbString,
221 property: &DbString,
222 candidates: &[NodeId],
223 k: usize,
224 checker: CancellationChecker<'_>,
225 mut predicate: impl FnMut(&JsonValue) -> Option<Option<JsonValue>>,
226 ) -> Result<Vec<(NodeId, Option<JsonValue>)>, JsonSearchError> {
227 checker.check()?;
228 if k == 0 || candidates.is_empty() {
229 return Ok(Vec::new());
230 }
231 let candidates = sorted_unique_candidates(candidates);
232 let mut hits = Vec::new();
233 for (offset, node_id) in candidates.into_iter().enumerate() {
234 if offset % JSON_SEARCH_CANCEL_STRIDE == 0 {
235 checker.check()?;
236 }
237 let Some(value) = self.json_candidate_value(label, property, node_id) else {
238 continue;
239 };
240 if let Some(selected) = predicate(value) {
241 hits.push((node_id, selected));
242 if hits.len() == k {
243 break;
244 }
245 }
246 }
247 Ok(hits)
248 }
249
250 fn json_candidate_value(
251 &self,
252 label: &DbString,
253 property: &DbString,
254 node_id: NodeId,
255 ) -> Option<&JsonValue> {
256 let labels = self.node_labels(node_id)?;
257 if !labels.contains(label) {
258 return None;
259 }
260 let properties = self.node_properties(node_id)?;
261 match properties.get(property) {
262 Some(Value::Json(value)) => Some(value),
263 _ => None,
264 }
265 }
266}
267
268impl SharedGraph {
269 pub fn exact_json_contains_candidate_nodes(
271 &self,
272 label: &DbString,
273 property: &DbString,
274 candidate: &JsonValue,
275 candidates: &[NodeId],
276 k: usize,
277 ) -> GraphResult<Vec<JsonContainmentHit>> {
278 self.read()
279 .exact_json_contains_candidate_nodes(label, property, candidate, candidates, k)
280 }
281
282 pub fn exact_json_contains_candidate_nodes_checked(
284 &self,
285 label: &DbString,
286 property: &DbString,
287 candidate: &JsonValue,
288 candidates: &[NodeId],
289 k: usize,
290 checker: CancellationChecker<'_>,
291 ) -> Result<Vec<JsonContainmentHit>, JsonSearchError> {
292 self.read().exact_json_contains_candidate_nodes_checked(
293 label, property, candidate, candidates, k, checker,
294 )
295 }
296
297 pub fn exact_json_path_exists_candidate_nodes(
299 &self,
300 label: &DbString,
301 property: &DbString,
302 path: &[JsonPathSelector],
303 candidates: &[NodeId],
304 k: usize,
305 ) -> GraphResult<Vec<JsonPathHit>> {
306 self.read()
307 .exact_json_path_exists_candidate_nodes(label, property, path, candidates, k)
308 }
309
310 pub fn exact_json_path_exists_candidate_nodes_checked(
312 &self,
313 label: &DbString,
314 property: &DbString,
315 path: &[JsonPathSelector],
316 candidates: &[NodeId],
317 k: usize,
318 checker: CancellationChecker<'_>,
319 ) -> Result<Vec<JsonPathHit>, JsonSearchError> {
320 self.read().exact_json_path_exists_candidate_nodes_checked(
321 label, property, path, candidates, k, checker,
322 )
323 }
324
325 pub fn exact_json_path_contains_candidate_nodes(
327 &self,
328 label: &DbString,
329 property: &DbString,
330 options: JsonPathContainmentCandidateOptions<'_>,
331 ) -> GraphResult<Vec<JsonPathContainmentHit>> {
332 self.read()
333 .exact_json_path_contains_candidate_nodes(label, property, options)
334 }
335
336 pub fn exact_json_path_contains_candidate_nodes_checked(
338 &self,
339 label: &DbString,
340 property: &DbString,
341 options: JsonPathContainmentCandidateOptions<'_>,
342 checker: CancellationChecker<'_>,
343 ) -> Result<Vec<JsonPathContainmentHit>, JsonSearchError> {
344 self.read()
345 .exact_json_path_contains_candidate_nodes_checked(label, property, options, checker)
346 }
347
348 pub fn exact_json_path_value_candidate_nodes(
350 &self,
351 label: &DbString,
352 property: &DbString,
353 path: &[JsonPathSelector],
354 candidates: &[NodeId],
355 k: usize,
356 ) -> GraphResult<Vec<JsonPathValueHit>> {
357 self.read()
358 .exact_json_path_value_candidate_nodes(label, property, path, candidates, k)
359 }
360
361 pub fn exact_json_path_value_candidate_nodes_checked(
363 &self,
364 label: &DbString,
365 property: &DbString,
366 path: &[JsonPathSelector],
367 candidates: &[NodeId],
368 k: usize,
369 checker: CancellationChecker<'_>,
370 ) -> Result<Vec<JsonPathValueHit>, JsonSearchError> {
371 self.read().exact_json_path_value_candidate_nodes_checked(
372 label, property, path, candidates, k, checker,
373 )
374 }
375}
376
377fn sorted_unique_candidates(candidates: &[NodeId]) -> Vec<NodeId> {
378 let mut candidates = candidates.to_vec();
379 candidates.sort_unstable();
380 candidates.dedup();
381 candidates
382}