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 let mut candidates_since_check = 0usize;
234 for node_id in candidates {
235 candidates_since_check += 1;
236 if candidates_since_check >= JSON_SEARCH_CANCEL_STRIDE {
237 checker.note_nodes_scanned(candidates_since_check)?;
238 candidates_since_check = 0;
239 }
240 let Some(value) = self.json_candidate_value(label, property, node_id) else {
241 continue;
242 };
243 if let Some(selected) = predicate(value) {
244 hits.push((node_id, selected));
245 if hits.len() == k {
246 break;
247 }
248 }
249 }
250 if candidates_since_check > 0 {
251 checker.note_nodes_scanned(candidates_since_check)?;
252 }
253 Ok(hits)
254 }
255
256 fn json_candidate_value(
257 &self,
258 label: &DbString,
259 property: &DbString,
260 node_id: NodeId,
261 ) -> Option<&JsonValue> {
262 let labels = self.node_labels(node_id)?;
263 if !labels.contains(label) {
264 return None;
265 }
266 let properties = self.node_properties(node_id)?;
267 match properties.get(property) {
268 Some(Value::Json(value)) => Some(value),
269 _ => None,
270 }
271 }
272}
273
274impl SharedGraph {
275 pub fn exact_json_contains_candidate_nodes(
277 &self,
278 label: &DbString,
279 property: &DbString,
280 candidate: &JsonValue,
281 candidates: &[NodeId],
282 k: usize,
283 ) -> GraphResult<Vec<JsonContainmentHit>> {
284 self.read()
285 .exact_json_contains_candidate_nodes(label, property, candidate, candidates, k)
286 }
287
288 pub fn exact_json_contains_candidate_nodes_checked(
290 &self,
291 label: &DbString,
292 property: &DbString,
293 candidate: &JsonValue,
294 candidates: &[NodeId],
295 k: usize,
296 checker: CancellationChecker<'_>,
297 ) -> Result<Vec<JsonContainmentHit>, JsonSearchError> {
298 self.read().exact_json_contains_candidate_nodes_checked(
299 label, property, candidate, candidates, k, checker,
300 )
301 }
302
303 pub fn exact_json_path_exists_candidate_nodes(
305 &self,
306 label: &DbString,
307 property: &DbString,
308 path: &[JsonPathSelector],
309 candidates: &[NodeId],
310 k: usize,
311 ) -> GraphResult<Vec<JsonPathHit>> {
312 self.read()
313 .exact_json_path_exists_candidate_nodes(label, property, path, candidates, k)
314 }
315
316 pub fn exact_json_path_exists_candidate_nodes_checked(
318 &self,
319 label: &DbString,
320 property: &DbString,
321 path: &[JsonPathSelector],
322 candidates: &[NodeId],
323 k: usize,
324 checker: CancellationChecker<'_>,
325 ) -> Result<Vec<JsonPathHit>, JsonSearchError> {
326 self.read().exact_json_path_exists_candidate_nodes_checked(
327 label, property, path, candidates, k, checker,
328 )
329 }
330
331 pub fn exact_json_path_contains_candidate_nodes(
333 &self,
334 label: &DbString,
335 property: &DbString,
336 options: JsonPathContainmentCandidateOptions<'_>,
337 ) -> GraphResult<Vec<JsonPathContainmentHit>> {
338 self.read()
339 .exact_json_path_contains_candidate_nodes(label, property, options)
340 }
341
342 pub fn exact_json_path_contains_candidate_nodes_checked(
344 &self,
345 label: &DbString,
346 property: &DbString,
347 options: JsonPathContainmentCandidateOptions<'_>,
348 checker: CancellationChecker<'_>,
349 ) -> Result<Vec<JsonPathContainmentHit>, JsonSearchError> {
350 self.read()
351 .exact_json_path_contains_candidate_nodes_checked(label, property, options, checker)
352 }
353
354 pub fn exact_json_path_value_candidate_nodes(
356 &self,
357 label: &DbString,
358 property: &DbString,
359 path: &[JsonPathSelector],
360 candidates: &[NodeId],
361 k: usize,
362 ) -> GraphResult<Vec<JsonPathValueHit>> {
363 self.read()
364 .exact_json_path_value_candidate_nodes(label, property, path, candidates, k)
365 }
366
367 pub fn exact_json_path_value_candidate_nodes_checked(
369 &self,
370 label: &DbString,
371 property: &DbString,
372 path: &[JsonPathSelector],
373 candidates: &[NodeId],
374 k: usize,
375 checker: CancellationChecker<'_>,
376 ) -> Result<Vec<JsonPathValueHit>, JsonSearchError> {
377 self.read().exact_json_path_value_candidate_nodes_checked(
378 label, property, path, candidates, k, checker,
379 )
380 }
381}
382
383fn sorted_unique_candidates(candidates: &[NodeId]) -> Vec<NodeId> {
384 let mut candidates = candidates.to_vec();
385 candidates.sort_unstable();
386 candidates.dedup();
387 candidates
388}