use std::collections::BTreeSet;
use lora_ast::Direction;
use crate::{
BorrowedGraphStorage, ConstraintDefinition, ConstraintRequest, CreateConstraintError,
CreateConstraintOutcome, CreateIndexError, CreateIndexOutcome, DropConstraintError,
DropConstraintOutcome, DropIndexError, DropIndexOutcome, GraphStats, GraphStorage,
GraphStorageMut, IndexDefinition, IndexRequest, LoraVector, MutationEvent, NodeId, NodeRecord,
Properties, PropertyValue, RelationshipId, RelationshipRecord, StoredIndexEntity,
};
use super::property_index::PropertyIndexKey;
use super::InMemoryGraph;
impl GraphStorage for InMemoryGraph {
fn list_indexes(&self) -> Vec<IndexDefinition> {
self.index_catalog_read().list()
}
fn get_index(&self, name: &str) -> Option<IndexDefinition> {
self.index_catalog_read().get(name).cloned()
}
fn fulltext_search(&self, name: &str, query: &str) -> Vec<(u64, f64)> {
let node_hits = self
.fulltext_indexes_read(StoredIndexEntity::Node)
.get(name)
.map(|idx| idx.query(query));
if let Some(hits) = node_hits {
if !hits.is_empty() {
return hits;
}
}
let rel_hits = self
.fulltext_indexes_read(StoredIndexEntity::Relationship)
.get(name)
.map(|idx| idx.query(query));
rel_hits.unwrap_or_default()
}
fn vector_search(
&self,
name: &str,
query: &LoraVector,
k: usize,
restrict_to: Option<&std::collections::BTreeSet<u64>>,
) -> Vec<(u64, f64)> {
self.lazy_populate_vector_index(name);
if let Some(hits) =
self.vector_indexes_read(StoredIndexEntity::Node)
.query(name, query, k, restrict_to)
{
if !hits.is_empty() {
return hits;
}
}
self.vector_indexes_read(StoredIndexEntity::Relationship)
.query(name, query, k, restrict_to)
.unwrap_or_default()
}
fn list_constraints(&self) -> Vec<ConstraintDefinition> {
self.constraint_catalog_read().list()
}
fn get_constraint(&self, name: &str) -> Option<ConstraintDefinition> {
self.constraint_catalog_read().get(name).cloned()
}
fn check_node_create_against_constraints(
&self,
labels: &[String],
properties: &Properties,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_node_create(&catalog, self, labels, properties)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_relationship_create_against_constraints(
&self,
rel_type: &str,
properties: &Properties,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_relationship_create(
&catalog, self, rel_type, properties,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_node_set_property_against_constraints(
&self,
node_id: NodeId,
key: &str,
value: &PropertyValue,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_node_set_property(
&catalog, self, node_id, key, value,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_node_remove_property_against_constraints(
&self,
node_id: NodeId,
key: &str,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_node_remove_property(&catalog, self, node_id, key)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_node_replace_properties_against_constraints(
&self,
node_id: NodeId,
properties: &Properties,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_node_replace_properties(
&catalog, self, node_id, properties,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_relationship_set_property_against_constraints(
&self,
rel_id: RelationshipId,
key: &str,
value: &PropertyValue,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_relationship_set_property(
&catalog, self, rel_id, key, value,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_relationship_remove_property_against_constraints(
&self,
rel_id: RelationshipId,
key: &str,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_relationship_remove_property(
&catalog, self, rel_id, key,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_relationship_replace_properties_against_constraints(
&self,
rel_id: RelationshipId,
properties: &Properties,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_relationship_replace_properties(
&catalog, self, rel_id, properties,
)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn check_node_add_label_against_constraints(
&self,
node_id: NodeId,
label: &str,
) -> Result<(), String> {
if !self.has_active_constraints() {
return Ok(());
}
let catalog = self.constraint_catalog_read();
crate::memory::constraint_enforce::check_node_add_label(&catalog, self, node_id, label)
.map_err(|e| format!("[{}] {e}", e.gql_status()))
}
fn graph_stats(&self) -> GraphStats {
InMemoryGraph::graph_stats(self)
}
fn node_text_candidates(
&self,
label: &str,
property: &str,
query: &str,
) -> Option<Vec<NodeId>> {
let registry = self.text_indexes_read(crate::StoredIndexEntity::Node);
let candidates = registry.candidates(label, property, query)?;
Some(candidates.into_iter().collect())
}
fn node_range_candidates(
&self,
label: &str,
property: &str,
lo: Option<&PropertyValue>,
hi: Option<&PropertyValue>,
) -> Option<Vec<NodeId>> {
let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Node);
let candidates = registry.range_candidates(label, property, lo, hi)?;
Some(candidates.into_iter().collect())
}
fn node_point_within_bbox(
&self,
label: &str,
property: &str,
ll: (f64, f64),
ur: (f64, f64),
) -> Option<Vec<NodeId>> {
let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
let candidates = registry.within_bbox(label, property, ll, ur)?;
Some(candidates.into_iter().collect())
}
fn node_point_within_distance(
&self,
label: &str,
property: &str,
center: (f64, f64),
max_distance: f64,
) -> Option<Vec<NodeId>> {
let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
let candidates = registry.within_distance(label, property, center, max_distance)?;
Some(candidates.into_iter().collect())
}
fn relationship_text_candidates(
&self,
rel_type: &str,
property: &str,
query: &str,
) -> Option<Vec<RelationshipId>> {
let registry = self.text_indexes_read(crate::StoredIndexEntity::Relationship);
let candidates = registry.candidates(rel_type, property, query)?;
Some(candidates.into_iter().collect())
}
fn relationship_range_candidates(
&self,
rel_type: &str,
property: &str,
lo: Option<&PropertyValue>,
hi: Option<&PropertyValue>,
) -> Option<Vec<RelationshipId>> {
let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Relationship);
let candidates = registry.range_candidates(rel_type, property, lo, hi)?;
Some(candidates.into_iter().collect())
}
fn relationship_point_within_bbox(
&self,
rel_type: &str,
property: &str,
ll: (f64, f64),
ur: (f64, f64),
) -> Option<Vec<RelationshipId>> {
let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
let candidates = registry.within_bbox(rel_type, property, ll, ur)?;
Some(candidates.into_iter().collect())
}
fn relationship_point_within_distance(
&self,
rel_type: &str,
property: &str,
center: (f64, f64),
max_distance: f64,
) -> Option<Vec<RelationshipId>> {
let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
let candidates = registry.within_distance(rel_type, property, center, max_distance)?;
Some(candidates.into_iter().collect())
}
fn contains_node(&self, id: NodeId) -> bool {
self.node_at(id).is_some()
}
fn node(&self, id: NodeId) -> Option<NodeRecord> {
self.node_at(id).cloned()
}
fn all_node_ids(&self) -> Vec<NodeId> {
self.iter_node_ids().collect()
}
fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
match self.nodes_by_label.get(label) {
Some(ids) => ids.clone(),
None => Vec::new(),
}
}
fn contains_relationship(&self, id: RelationshipId) -> bool {
self.rel_at(id).is_some()
}
fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
self.rel_at(id).cloned()
}
fn all_rel_ids(&self) -> Vec<RelationshipId> {
self.iter_rel_ids().collect()
}
fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
match self.relationships_by_type.get(rel_type) {
Some(ids) => ids.clone(),
None => Vec::new(),
}
}
fn relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
self.rel_at(id).map(|r| (r.src, r.dst))
}
fn expand_ids(
&self,
node_id: NodeId,
direction: Direction,
types: &[String],
) -> Vec<(RelationshipId, NodeId)> {
if self.node_at(node_id).is_none() {
return Vec::new();
}
let mut out: Vec<(RelationshipId, NodeId)> = Vec::new();
let single_type = match types {
[single] => Some(single.as_str()),
_ => None,
};
let has_type_filter = !types.is_empty();
let push_from = |adj: &[RelationshipId],
skip_self_loops: bool,
out: &mut Vec<(RelationshipId, NodeId)>| {
for &rel_id in adj {
let Some(rel) = self.rel_at(rel_id) else {
continue;
};
if skip_self_loops && rel.src == node_id && rel.dst == node_id {
continue;
}
if let Some(single) = single_type {
if rel.rel_type != single {
continue;
}
} else if has_type_filter && !types.iter().any(|t| t == &rel.rel_type) {
continue;
}
let Some(other_id) = Self::other_endpoint(rel, node_id) else {
continue;
};
out.push((rel_id, other_id));
}
};
match direction {
Direction::Right => {
if let Some(adj) = self.outgoing_at(node_id) {
out.reserve(adj.len());
push_from(adj, false, &mut out);
}
}
Direction::Left => {
if let Some(adj) = self.incoming_at(node_id) {
out.reserve(adj.len());
push_from(adj, false, &mut out);
}
}
Direction::Undirected => {
let out_len = self.outgoing_at(node_id).map(<[_]>::len).unwrap_or(0);
let in_len = self.incoming_at(node_id).map(<[_]>::len).unwrap_or(0);
out.reserve(out_len + in_len);
if let Some(adj) = self.outgoing_at(node_id) {
push_from(adj, false, &mut out);
}
if let Some(adj) = self.incoming_at(node_id) {
push_from(adj, true, &mut out);
}
}
}
out
}
fn try_for_each_expand_id<F, E>(
&self,
node_id: NodeId,
direction: Direction,
types: &[String],
visit: F,
) -> Result<(), E>
where
F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
Self: Sized,
{
self.try_for_each_adjacent_id(node_id, direction, types, visit)
}
fn all_labels(&self) -> Vec<String> {
self.nodes_by_label.keys().cloned().collect()
}
fn all_relationship_types(&self) -> Vec<String> {
self.relationships_by_type.keys().cloned().collect()
}
fn with_node<F, R>(&self, id: NodeId, f: F) -> Option<R>
where
F: FnOnce(&NodeRecord) -> R,
Self: Sized,
{
self.node_at(id).map(f)
}
fn with_relationship<F, R>(&self, id: RelationshipId, f: F) -> Option<R>
where
F: FnOnce(&RelationshipRecord) -> R,
Self: Sized,
{
self.rel_at(id).map(f)
}
fn has_node(&self, id: NodeId) -> bool {
self.node_at(id).is_some()
}
fn has_relationship(&self, id: RelationshipId) -> bool {
self.rel_at(id).is_some()
}
fn node_count(&self) -> usize {
self.live_node_count
}
fn relationship_count(&self) -> usize {
self.live_rel_count
}
fn all_nodes(&self) -> Vec<NodeRecord> {
self.iter_node_records().cloned().collect()
}
fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
self.nodes_by_label
.get(label)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.node_at(id).cloned())
.collect()
}
fn all_relationships(&self) -> Vec<RelationshipRecord> {
self.iter_rel_records().cloned().collect()
}
fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
self.relationships_by_type
.get(rel_type)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.rel_at(id).cloned())
.collect()
}
fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
self.relationship_ids_for_direction(node_id, direction)
}
fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
self.outgoing_at(node_id)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.rel_at(id).cloned())
.collect()
}
fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
self.incoming_at(node_id)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.rel_at(id).cloned())
.collect()
}
fn relationships_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipRecord> {
let mut out = Vec::new();
let _ = self.try_for_each_expand_id(node_id, direction, &[], |rel_id, _| {
if let Some(rel) = self.rel_at(rel_id) {
out.push(rel.clone());
}
Ok::<(), ()>(())
});
out
}
fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
match direction {
Direction::Right => self.outgoing_at(node_id).map(|s| s.len()).unwrap_or(0),
Direction::Left => self.incoming_at(node_id).map(|s| s.len()).unwrap_or(0),
Direction::Undirected => {
let out_count = self.outgoing_at(node_id).map(<[_]>::len).unwrap_or(0);
let incoming_non_self = self
.incoming_at(node_id)
.into_iter()
.flat_map(|ids| ids.iter())
.filter(|&&rel_id| {
self.rel_at(rel_id)
.map(|rel| rel.src != node_id || rel.dst != node_id)
.unwrap_or(false)
})
.count();
out_count + incoming_non_self
}
}
}
fn expand(
&self,
node_id: NodeId,
direction: Direction,
types: &[String],
) -> Vec<(RelationshipRecord, NodeRecord)> {
if self.node_at(node_id).is_none() {
return Vec::new();
}
let mut out = Vec::new();
let _ = self.try_for_each_expand_id(node_id, direction, types, |rel_id, other_id| {
if let (Some(rel), Some(other)) = (self.rel_at(rel_id), self.node_at(other_id)) {
out.push((rel.clone(), other.clone()));
}
Ok::<(), ()>(())
});
out
}
fn neighbors(
&self,
node_id: NodeId,
direction: Direction,
types: &[String],
) -> Vec<NodeRecord> {
let mut out = Vec::new();
let _ = self.try_for_each_expand_id(node_id, direction, types, |_, other_id| {
if let Some(node) = self.node_at(other_id) {
out.push(node.clone());
}
Ok::<(), ()>(())
});
out
}
fn all_node_property_keys(&self) -> Vec<String> {
let mut keys = BTreeSet::new();
for node in self.iter_node_records() {
for key in node.properties.keys() {
keys.insert(key.clone());
}
}
keys.into_iter().collect()
}
fn all_relationship_property_keys(&self) -> Vec<String> {
let mut keys = BTreeSet::new();
for rel in self.iter_rel_records() {
for key in rel.properties.keys() {
keys.insert(key.clone());
}
}
keys.into_iter().collect()
}
fn label_property_keys(&self, label: &str) -> Vec<String> {
let mut keys = BTreeSet::new();
if let Some(ids) = self.nodes_by_label.get(label) {
for &id in ids {
if let Some(node) = self.node_at(id) {
for key in node.properties.keys() {
keys.insert(key.clone());
}
}
}
}
keys.into_iter().collect()
}
fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
let mut keys = BTreeSet::new();
if let Some(ids) = self.relationships_by_type.get(rel_type) {
for &id in ids {
if let Some(rel) = self.rel_at(id) {
for key in rel.properties.keys() {
keys.insert(key.clone());
}
}
}
}
keys.into_iter().collect()
}
fn find_nodes_by_property(
&self,
label: Option<&str>,
key: &str,
value: &PropertyValue,
) -> Vec<NodeRecord>
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.scan_nodes_by_property(label, key, value);
}
self.ensure_node_property_index(key);
let indexes = self.indexes_read();
match label {
Some(label) => {
let Some(ids) = indexes.node_properties.scoped_ids_for(label, key, value) else {
return Vec::new();
};
ids.iter()
.filter_map(|&id| self.node_at(id).cloned())
.collect()
}
None => indexes
.node_properties
.ids_for(key, value)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.node_at(id).cloned())
.collect(),
}
}
fn find_node_ids_by_property(
&self,
label: Option<&str>,
key: &str,
value: &PropertyValue,
) -> Vec<NodeId>
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.scan_node_ids_by_property(label, key, value);
}
self.ensure_node_property_index(key);
let indexes = self.indexes_read();
match label {
Some(label) => indexes
.node_properties
.scoped_ids_for(label, key, value)
.map(|ids| ids.to_vec())
.unwrap_or_default(),
None => indexes
.node_properties
.ids_for(key, value)
.map(|ids| ids.to_vec())
.unwrap_or_default(),
}
}
fn find_relationships_by_property(
&self,
rel_type: Option<&str>,
key: &str,
value: &PropertyValue,
) -> Vec<RelationshipRecord>
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.scan_relationships_by_property(rel_type, key, value);
}
self.ensure_relationship_property_index(key);
let indexes = self.indexes_read();
match rel_type {
Some(rel_type) => {
let Some(ids) = indexes
.relationship_properties
.scoped_ids_for(rel_type, key, value)
else {
return Vec::new();
};
ids.iter()
.filter_map(|&id| self.rel_at(id).cloned())
.collect()
}
None => indexes
.relationship_properties
.ids_for(key, value)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.rel_at(id).cloned())
.collect(),
}
}
fn find_relationship_ids_by_property(
&self,
rel_type: Option<&str>,
key: &str,
value: &PropertyValue,
) -> Vec<RelationshipId>
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.scan_relationship_ids_by_property(rel_type, key, value);
}
self.ensure_relationship_property_index(key);
let indexes = self.indexes_read();
match rel_type {
Some(rel_type) => indexes
.relationship_properties
.scoped_ids_for(rel_type, key, value)
.map(|ids| ids.to_vec())
.unwrap_or_default(),
None => indexes
.relationship_properties
.ids_for(key, value)
.map(|ids| ids.to_vec())
.unwrap_or_default(),
}
}
fn node_exists_with_label_and_property(
&self,
label: &str,
key: &str,
value: &PropertyValue,
) -> bool
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.any_node_by_property(label, key, value);
}
self.ensure_node_property_index(key);
let indexes = self.indexes_read();
indexes
.node_properties
.scoped_ids_for(label, key, value)
.map(|ids| !ids.is_empty())
.unwrap_or(false)
}
fn relationship_exists_with_type_and_property(
&self,
rel_type: &str,
key: &str,
value: &PropertyValue,
) -> bool
where
Self: Sized,
{
if PropertyIndexKey::from_value(value).is_none() {
return self.any_relationship_by_property(rel_type, key, value);
}
self.ensure_relationship_property_index(key);
let indexes = self.indexes_read();
indexes
.relationship_properties
.scoped_ids_for(rel_type, key, value)
.map(|ids| !ids.is_empty())
.unwrap_or(false)
}
}
impl BorrowedGraphStorage for InMemoryGraph {
fn node_ref(&self, id: NodeId) -> Option<&NodeRecord> {
self.node_at(id)
}
fn relationship_ref(&self, id: RelationshipId) -> Option<&RelationshipRecord> {
self.rel_at(id)
}
fn node_refs(&self) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
Box::new(self.iter_node_records())
}
fn node_refs_by_label(&self, label: &str) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
Box::new(
self.nodes_by_label
.get(label)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.node_at(id)),
)
}
fn relationship_refs(&self) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
Box::new(self.iter_rel_records())
}
fn relationship_refs_by_type(
&self,
rel_type: &str,
) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
Box::new(
self.relationships_by_type
.get(rel_type)
.into_iter()
.flat_map(|ids| ids.iter())
.filter_map(|&id| self.rel_at(id)),
)
}
}
impl GraphStorageMut for InMemoryGraph {
fn create_node(&mut self, labels: Vec<String>, properties: Properties) -> NodeRecord {
let (id, idx) = self.reserve_next_node_slot();
let labels = Self::normalize_labels(labels);
let node = NodeRecord {
id,
labels: labels.clone(),
properties,
};
self.put_node_at_slot(idx, node.clone());
self.on_node_created(&node);
self.emit(|| MutationEvent::CreateNode {
id,
labels: node.labels.clone(),
properties: node.properties.clone(),
});
node
}
fn create_relationship(
&mut self,
src: NodeId,
dst: NodeId,
rel_type: &str,
properties: Properties,
) -> Option<RelationshipRecord> {
if self.node_at(src).is_none() || self.node_at(dst).is_none() {
return None;
}
let trimmed = rel_type.trim();
if trimmed.is_empty() {
return None;
}
let (id, idx) = self.try_reserve_next_rel_slot()?;
let rel = RelationshipRecord {
id,
src,
dst,
rel_type: trimmed.to_string(),
properties,
};
self.put_rel_at_slot(idx, rel.clone());
self.on_relationship_created(&rel);
self.emit(|| MutationEvent::CreateRelationship {
id,
src,
dst,
rel_type: rel.rel_type.clone(),
properties: rel.properties.clone(),
});
Some(rel)
}
fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
if self.node_at(node_id).is_none() {
return false;
}
let old = match self.node_at_mut(node_id) {
Some(node) => node.properties.insert(key.clone(), value.clone()),
None => return false,
};
self.on_node_property_set(node_id, &key, old.as_ref(), &value);
self.emit(|| MutationEvent::SetNodeProperty {
node_id,
key: key.clone(),
value: value.clone(),
});
true
}
fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
let removed = match self.node_at_mut(node_id) {
Some(node) => node.properties.remove(key),
None => return false,
};
let Some(removed) = removed else {
return false;
};
self.on_node_property_removed(node_id, key, &removed);
self.emit(|| MutationEvent::RemoveNodeProperty {
node_id,
key: key.to_string(),
});
true
}
fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
let label = label.trim();
if label.is_empty() {
return false;
}
let applied = match self.node_at_mut(node_id) {
Some(node) => {
if node.labels.iter().any(|l| l == label) {
return false;
}
node.labels.push(label.to_string());
true
}
None => false,
};
if applied {
self.on_node_label_added(node_id, label);
self.emit(|| MutationEvent::AddNodeLabel {
node_id,
label: label.to_string(),
});
}
applied
}
fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
let applied = match self.node_at_mut(node_id) {
Some(node) => {
let original_len = node.labels.len();
node.labels.retain(|l| l != label);
node.labels.len() != original_len
}
None => false,
};
if applied {
self.on_node_label_removed(node_id, label);
self.emit(|| MutationEvent::RemoveNodeLabel {
node_id,
label: label.to_string(),
});
}
applied
}
fn set_relationship_property(
&mut self,
rel_id: RelationshipId,
key: String,
value: PropertyValue,
) -> bool {
if self.rel_at(rel_id).is_none() {
return false;
}
let old = match self.rel_at_mut(rel_id) {
Some(rel) => rel.properties.insert(key.clone(), value.clone()),
None => return false,
};
self.on_relationship_property_set(rel_id, &key, old.as_ref(), &value);
self.emit(|| MutationEvent::SetRelationshipProperty {
rel_id,
key: key.clone(),
value: value.clone(),
});
true
}
fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
let removed = match self.rel_at_mut(rel_id) {
Some(rel) => rel.properties.remove(key),
None => return false,
};
let Some(removed) = removed else {
return false;
};
self.on_relationship_property_removed(rel_id, key, &removed);
self.emit(|| MutationEvent::RemoveRelationshipProperty {
rel_id,
key: key.to_string(),
});
true
}
fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
let applied = match self.take_rel(rel_id) {
Some(rel) => {
self.on_relationship_deleted(&rel);
true
}
None => false,
};
if applied {
self.emit(|| MutationEvent::DeleteRelationship { rel_id });
}
applied
}
fn delete_node(&mut self, node_id: NodeId) -> bool {
if self.node_at(node_id).is_none() {
return false;
}
if self.has_incident_relationships(node_id) {
return false;
}
let node = match self.take_node(node_id) {
Some(node) => node,
None => return false,
};
self.on_node_deleted(&node);
self.emit(|| MutationEvent::DeleteNode { node_id });
true
}
fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
if self.node_at(node_id).is_none() {
return false;
}
let rel_ids: Vec<_> = self
.incident_relationship_ids(node_id)
.into_iter()
.collect();
for rel_id in rel_ids {
let _ = self.delete_relationship(rel_id);
}
if self.delete_node(node_id) {
self.emit(|| MutationEvent::DetachDeleteNode { node_id });
true
} else {
false
}
}
fn clear(&mut self) {
let recorder = self.recorder.take();
*self = Self::default();
self.recorder = recorder;
self.emit(|| MutationEvent::Clear);
}
fn create_index(
&mut self,
request: IndexRequest,
if_not_exists: bool,
) -> Result<CreateIndexOutcome, CreateIndexError> {
self.register_index(request, if_not_exists)
}
fn drop_index(
&mut self,
name: &str,
if_exists: bool,
) -> Result<DropIndexOutcome, DropIndexError> {
self.drop_named_index(name, if_exists)
}
fn create_constraint(
&mut self,
request: ConstraintRequest,
if_not_exists: bool,
) -> Result<CreateConstraintOutcome, CreateConstraintError> {
self.register_constraint(request, if_not_exists)
}
fn drop_constraint(
&mut self,
name: &str,
if_exists: bool,
) -> Result<DropConstraintOutcome, DropConstraintError> {
self.drop_named_constraint(name, if_exists)
}
}