use selene_core::{CancellationChecker, DbString, JsonPathSelector, JsonValue, NodeId, Value};
use crate::error::GraphResult;
use crate::graph::SeleneGraph;
use crate::json_search::{
JSON_SEARCH_CANCEL_STRIDE, JsonContainmentHit, JsonPathContainmentHit, JsonPathHit,
JsonPathValueHit, JsonSearchError,
};
use crate::shared::SharedGraph;
#[derive(Clone, Copy, Debug)]
pub struct JsonPathContainmentCandidateOptions<'a> {
pub path: &'a [JsonPathSelector],
pub candidate: &'a JsonValue,
pub candidates: &'a [NodeId],
pub k: usize,
}
impl<'a> JsonPathContainmentCandidateOptions<'a> {
#[must_use]
pub const fn new(
path: &'a [JsonPathSelector],
candidate: &'a JsonValue,
candidates: &'a [NodeId],
k: usize,
) -> Self {
Self {
path,
candidate,
candidates,
k,
}
}
}
impl SeleneGraph {
pub fn exact_json_contains_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
candidate: &JsonValue,
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonContainmentHit>> {
self.exact_json_contains_candidate_nodes_checked(
label,
property,
candidate,
candidates,
k,
CancellationChecker::disabled(),
)
.map_err(JsonSearchError::into_graph_error)
}
pub fn exact_json_contains_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
candidate: &JsonValue,
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonContainmentHit>, JsonSearchError> {
self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
json.contains(candidate).then_some(None)
})
.map(|hits| {
hits.into_iter()
.map(|(node_id, _)| JsonContainmentHit { node_id })
.collect()
})
}
pub fn exact_json_path_exists_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonPathHit>> {
self.exact_json_path_exists_candidate_nodes_checked(
label,
property,
path,
candidates,
k,
CancellationChecker::disabled(),
)
.map_err(JsonSearchError::into_graph_error)
}
pub fn exact_json_path_exists_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathHit>, JsonSearchError> {
if path.is_empty() {
return Ok(Vec::new());
}
self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
json.path_exists(path).then_some(None)
})
.map(|hits| {
hits.into_iter()
.map(|(node_id, _)| JsonPathHit { node_id })
.collect()
})
}
pub fn exact_json_path_contains_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
options: JsonPathContainmentCandidateOptions<'_>,
) -> GraphResult<Vec<JsonPathContainmentHit>> {
self.exact_json_path_contains_candidate_nodes_checked(
label,
property,
options,
CancellationChecker::disabled(),
)
.map_err(JsonSearchError::into_graph_error)
}
pub fn exact_json_path_contains_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
options: JsonPathContainmentCandidateOptions<'_>,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathContainmentHit>, JsonSearchError> {
if options.path.is_empty() {
return Ok(Vec::new());
}
self.filter_json_candidate_nodes(
label,
property,
options.candidates,
options.k,
checker,
|json| {
json.path_contains(options.path, options.candidate)
.then_some(None)
},
)
.map(|hits| {
hits.into_iter()
.map(|(node_id, _)| JsonPathContainmentHit { node_id })
.collect()
})
}
pub fn exact_json_path_value_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonPathValueHit>> {
self.exact_json_path_value_candidate_nodes_checked(
label,
property,
path,
candidates,
k,
CancellationChecker::disabled(),
)
.map_err(JsonSearchError::into_graph_error)
}
pub fn exact_json_path_value_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathValueHit>, JsonSearchError> {
if path.is_empty() {
return Ok(Vec::new());
}
self.filter_json_candidate_nodes(label, property, candidates, k, checker, |json| {
json.path_value(path).map(Some)
})
.map(|hits| {
hits.into_iter()
.filter_map(|(node_id, value)| {
value.map(|value| JsonPathValueHit { node_id, value })
})
.collect()
})
}
fn filter_json_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
mut predicate: impl FnMut(&JsonValue) -> Option<Option<JsonValue>>,
) -> Result<Vec<(NodeId, Option<JsonValue>)>, JsonSearchError> {
checker.check()?;
if k == 0 || candidates.is_empty() {
return Ok(Vec::new());
}
let candidates = sorted_unique_candidates(candidates);
let mut hits = Vec::new();
for (offset, node_id) in candidates.into_iter().enumerate() {
if offset % JSON_SEARCH_CANCEL_STRIDE == 0 {
checker.check()?;
}
let Some(value) = self.json_candidate_value(label, property, node_id) else {
continue;
};
if let Some(selected) = predicate(value) {
hits.push((node_id, selected));
if hits.len() == k {
break;
}
}
}
Ok(hits)
}
fn json_candidate_value(
&self,
label: &DbString,
property: &DbString,
node_id: NodeId,
) -> Option<&JsonValue> {
let labels = self.node_labels(node_id)?;
if !labels.contains(label) {
return None;
}
let properties = self.node_properties(node_id)?;
match properties.get(property) {
Some(Value::Json(value)) => Some(value),
_ => None,
}
}
}
impl SharedGraph {
pub fn exact_json_contains_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
candidate: &JsonValue,
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonContainmentHit>> {
self.read()
.exact_json_contains_candidate_nodes(label, property, candidate, candidates, k)
}
pub fn exact_json_contains_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
candidate: &JsonValue,
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonContainmentHit>, JsonSearchError> {
self.read().exact_json_contains_candidate_nodes_checked(
label, property, candidate, candidates, k, checker,
)
}
pub fn exact_json_path_exists_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonPathHit>> {
self.read()
.exact_json_path_exists_candidate_nodes(label, property, path, candidates, k)
}
pub fn exact_json_path_exists_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathHit>, JsonSearchError> {
self.read().exact_json_path_exists_candidate_nodes_checked(
label, property, path, candidates, k, checker,
)
}
pub fn exact_json_path_contains_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
options: JsonPathContainmentCandidateOptions<'_>,
) -> GraphResult<Vec<JsonPathContainmentHit>> {
self.read()
.exact_json_path_contains_candidate_nodes(label, property, options)
}
pub fn exact_json_path_contains_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
options: JsonPathContainmentCandidateOptions<'_>,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathContainmentHit>, JsonSearchError> {
self.read()
.exact_json_path_contains_candidate_nodes_checked(label, property, options, checker)
}
pub fn exact_json_path_value_candidate_nodes(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
) -> GraphResult<Vec<JsonPathValueHit>> {
self.read()
.exact_json_path_value_candidate_nodes(label, property, path, candidates, k)
}
pub fn exact_json_path_value_candidate_nodes_checked(
&self,
label: &DbString,
property: &DbString,
path: &[JsonPathSelector],
candidates: &[NodeId],
k: usize,
checker: CancellationChecker<'_>,
) -> Result<Vec<JsonPathValueHit>, JsonSearchError> {
self.read().exact_json_path_value_candidate_nodes_checked(
label, property, path, candidates, k, checker,
)
}
}
fn sorted_unique_candidates(candidates: &[NodeId]) -> Vec<NodeId> {
let mut candidates = candidates.to_vec();
candidates.sort_unstable();
candidates.dedup();
candidates
}