use petgraph::graph::NodeIndex;
use crate::graph::schema::{DirGraph, InternedKey};
impl DirGraph {
pub fn add_node_label(&mut self, idx: NodeIndex, label: InternedKey) -> bool {
use crate::graph::storage::GraphRead;
let primary = match GraphRead::node_type_of(&self.graph, idx) {
Some(k) => k,
None => return false,
};
if primary == label {
return false;
}
let bucket_was_new = !self.secondary_label_index.contains_key(&label);
let bucket = self.secondary_label_index.entry(label).or_default();
if bucket.contains(&idx) {
return false;
}
bucket.push(idx);
self.has_secondary_labels = true;
if let Some(journal) = self.graph.undo_journal_mut() {
journal.note_bucket_appended(
crate::graph::storage::undo::BucketId::SecondaryLabel(label),
idx,
bucket_was_new,
);
}
self.graph.note_recorded_node_labels(idx);
true
}
pub fn remove_node_label(
&mut self,
idx: NodeIndex,
label: InternedKey,
) -> Result<bool, String> {
use crate::graph::storage::GraphRead;
let Some(primary) = GraphRead::node_type_of(&self.graph, idx) else {
return Ok(false);
};
if primary == label {
return Err(
"Cannot remove a node's primary label via REMOVE n:Label; use \
SET n.type = 'NewType' to retype."
.to_string(),
);
}
let Some(bucket) = self.secondary_label_index.get_mut(&label) else {
return Ok(false);
};
let position = bucket.iter().position(|&i| i == idx);
if let Some(pos) = position {
bucket.remove(pos);
}
if position.is_some() && bucket.is_empty() {
self.secondary_label_index.remove(&label);
}
if self.secondary_label_index.is_empty() {
self.has_secondary_labels = false;
}
if let Some(pos) = position {
if let Some(journal) = self.graph.undo_journal_mut() {
journal.note_bucket_removed(
crate::graph::storage::undo::BucketId::SecondaryLabel(label),
idx,
pos,
);
}
self.graph.note_recorded_node_labels(idx);
}
Ok(position.is_some())
}
pub fn node_labels(&self, idx: NodeIndex) -> Vec<InternedKey> {
use crate::graph::storage::GraphRead;
let Some(primary) = GraphRead::node_type_of(&self.graph, idx) else {
return Vec::new();
};
let extras = self.secondary_labels(idx);
let mut labels = Vec::with_capacity(extras.len() + 1);
labels.push(primary);
labels.extend(extras);
labels
}
pub fn secondary_labels(&self, idx: NodeIndex) -> Vec<InternedKey> {
if !self.has_secondary_labels {
return Vec::new();
}
let mut extras: Vec<InternedKey> = self
.secondary_label_index
.iter()
.filter(|(_, bucket)| bucket.contains(&idx))
.map(|(&key, _)| key)
.collect();
extras.sort_unstable_by(|a, b| self.interner.resolve(*a).cmp(self.interner.resolve(*b)));
extras
}
pub fn secondary_label_names(&self, idx: NodeIndex) -> Vec<String> {
self.secondary_labels(idx)
.into_iter()
.map(|key| self.interner.resolve(key).to_string())
.collect()
}
pub fn nodes_with_label(&self, label: &str) -> Vec<NodeIndex> {
let mut out = self
.type_indices
.get(label)
.map(|v| v.to_vec())
.unwrap_or_default();
if self.has_secondary_labels {
if let Some(secondary) = self
.secondary_label_index
.get(&InternedKey::from_str(label))
{
out.extend(secondary.iter().copied());
}
}
out
}
pub fn node_has_label(&self, idx: NodeIndex, key: InternedKey) -> bool {
use crate::graph::storage::GraphRead;
if GraphRead::node_type_of(&self.graph, idx) == Some(key) {
return true;
}
self.has_secondary_labels
&& self
.secondary_label_index
.get(&key)
.is_some_and(|bucket| bucket.contains(&idx))
}
}