Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
235
vendor/ruvector/crates/ruvector-delta-graph/src/edge_delta.rs
vendored
Normal file
235
vendor/ruvector/crates/ruvector-delta-graph/src/edge_delta.rs
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
//! Edge delta operations
|
||||
//!
|
||||
//! Represents changes to edges in a graph.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ruvector_delta_core::VectorDelta;
|
||||
|
||||
use crate::{EdgeId, NodeId, PropertyOp, PropertyValue};
|
||||
|
||||
/// An operation on an edge
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EdgeOp {
|
||||
/// Add a new edge
|
||||
Add {
|
||||
source: NodeId,
|
||||
target: NodeId,
|
||||
edge_type: String,
|
||||
properties: HashMap<String, PropertyValue>,
|
||||
},
|
||||
/// Remove an edge
|
||||
Remove,
|
||||
/// Update edge properties
|
||||
Update(Vec<PropertyDelta>),
|
||||
/// Change edge type
|
||||
Retype { new_type: String },
|
||||
}
|
||||
|
||||
/// A property delta for edges
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PropertyDelta {
|
||||
/// Property key
|
||||
pub key: String,
|
||||
/// Operation
|
||||
pub operation: PropertyOp,
|
||||
}
|
||||
|
||||
impl PropertyDelta {
|
||||
/// Create a set operation
|
||||
pub fn set(key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
operation: PropertyOp::Set(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a remove operation
|
||||
pub fn remove(key: impl Into<String>) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
operation: PropertyOp::Remove,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delta for an edge
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EdgeDelta {
|
||||
/// Property deltas
|
||||
pub property_deltas: Vec<PropertyDelta>,
|
||||
/// Weight change (if applicable)
|
||||
pub weight_delta: Option<f64>,
|
||||
/// Type change
|
||||
pub type_change: Option<String>,
|
||||
}
|
||||
|
||||
impl EdgeDelta {
|
||||
/// Create empty delta
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
property_deltas: Vec::new(),
|
||||
weight_delta: None,
|
||||
type_change: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.property_deltas.is_empty() && self.weight_delta.is_none() && self.type_change.is_none()
|
||||
}
|
||||
|
||||
/// Add a property set
|
||||
pub fn set_property(mut self, key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
self.property_deltas.push(PropertyDelta::set(key, value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a property removal
|
||||
pub fn remove_property(mut self, key: impl Into<String>) -> Self {
|
||||
self.property_deltas.push(PropertyDelta::remove(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set weight delta
|
||||
pub fn with_weight_delta(mut self, delta: f64) -> Self {
|
||||
self.weight_delta = Some(delta);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set type change
|
||||
pub fn with_type_change(mut self, new_type: impl Into<String>) -> Self {
|
||||
self.type_change = Some(new_type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Compose with another delta
|
||||
pub fn compose(mut self, other: EdgeDelta) -> Self {
|
||||
// Merge property deltas
|
||||
let mut prop_map: HashMap<String, PropertyDelta> = HashMap::new();
|
||||
for pd in self.property_deltas {
|
||||
prop_map.insert(pd.key.clone(), pd);
|
||||
}
|
||||
for pd in other.property_deltas {
|
||||
prop_map.insert(pd.key.clone(), pd);
|
||||
}
|
||||
self.property_deltas = prop_map.into_values().collect();
|
||||
|
||||
// Compose weight delta
|
||||
if let Some(od) = other.weight_delta {
|
||||
self.weight_delta = Some(self.weight_delta.unwrap_or(0.0) + od);
|
||||
}
|
||||
|
||||
// Take latest type change
|
||||
if other.type_change.is_some() {
|
||||
self.type_change = other.type_change;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Compute inverse
|
||||
pub fn inverse(&self) -> Self {
|
||||
Self {
|
||||
property_deltas: Vec::new(), // Can't invert without originals
|
||||
weight_delta: self.weight_delta.map(|d| -d),
|
||||
type_change: None, // Can't invert without original type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EdgeDelta {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for edge operations
|
||||
pub struct EdgeDeltaBuilder {
|
||||
delta: EdgeDelta,
|
||||
}
|
||||
|
||||
impl EdgeDeltaBuilder {
|
||||
/// Create new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
delta: EdgeDelta::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a property
|
||||
pub fn set(mut self, key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
self.delta
|
||||
.property_deltas
|
||||
.push(PropertyDelta::set(key, value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a property
|
||||
pub fn remove(mut self, key: impl Into<String>) -> Self {
|
||||
self.delta.property_deltas.push(PropertyDelta::remove(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set weight delta
|
||||
pub fn weight(mut self, delta: f64) -> Self {
|
||||
self.delta.weight_delta = Some(delta);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set type change
|
||||
pub fn retype(mut self, new_type: impl Into<String>) -> Self {
|
||||
self.delta.type_change = Some(new_type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the delta
|
||||
pub fn build(self) -> EdgeDelta {
|
||||
self.delta
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EdgeDeltaBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_edge_delta_builder() {
|
||||
let delta = EdgeDeltaBuilder::new()
|
||||
.set("weight", PropertyValue::Float(1.5))
|
||||
.set("label", PropertyValue::String("test".into()))
|
||||
.build();
|
||||
|
||||
assert_eq!(delta.property_deltas.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_delta_compose() {
|
||||
let d1 = EdgeDelta::new()
|
||||
.set_property("a", PropertyValue::Int(1))
|
||||
.with_weight_delta(1.0);
|
||||
|
||||
let d2 = EdgeDelta::new()
|
||||
.set_property("b", PropertyValue::Int(2))
|
||||
.with_weight_delta(0.5);
|
||||
|
||||
let composed = d1.compose(d2);
|
||||
|
||||
assert_eq!(composed.property_deltas.len(), 2);
|
||||
assert!((composed.weight_delta.unwrap() - 1.5).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_delta_inverse() {
|
||||
let delta = EdgeDelta::new().with_weight_delta(2.0);
|
||||
let inverse = delta.inverse();
|
||||
|
||||
assert!((inverse.weight_delta.unwrap() - (-2.0)).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
38
vendor/ruvector/crates/ruvector-delta-graph/src/error.rs
vendored
Normal file
38
vendor/ruvector/crates/ruvector-delta-graph/src/error.rs
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//! Error types for graph delta operations
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Result type for graph delta operations
|
||||
pub type Result<T> = std::result::Result<T, GraphDeltaError>;
|
||||
|
||||
/// Errors that can occur during graph delta operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GraphDeltaError {
|
||||
/// Node not found
|
||||
NodeNotFound(String),
|
||||
/// Edge not found
|
||||
EdgeNotFound(String),
|
||||
/// Invalid operation
|
||||
InvalidOperation(String),
|
||||
/// Delta error
|
||||
DeltaError(String),
|
||||
/// Cycle detected
|
||||
CycleDetected,
|
||||
/// Constraint violation
|
||||
ConstraintViolation(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for GraphDeltaError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NodeNotFound(id) => write!(f, "Node not found: {}", id),
|
||||
Self::EdgeNotFound(id) => write!(f, "Edge not found: {}", id),
|
||||
Self::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
|
||||
Self::DeltaError(msg) => write!(f, "Delta error: {}", msg),
|
||||
Self::CycleDetected => write!(f, "Cycle detected in graph"),
|
||||
Self::ConstraintViolation(msg) => write!(f, "Constraint violation: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for GraphDeltaError {}
|
||||
562
vendor/ruvector/crates/ruvector-delta-graph/src/lib.rs
vendored
Normal file
562
vendor/ruvector/crates/ruvector-delta-graph/src/lib.rs
vendored
Normal file
@@ -0,0 +1,562 @@
|
||||
//! # RuVector Delta Graph
|
||||
//!
|
||||
//! Delta operations for graph structures, supporting incremental updates
|
||||
//! to edges and node properties.
|
||||
//!
|
||||
//! ## Key Features
|
||||
//!
|
||||
//! - Edge delta operations (add, remove, update weight)
|
||||
//! - Node property deltas (including vector properties)
|
||||
//! - Delta-aware graph traversal
|
||||
//! - Efficient batch updates
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod edge_delta;
|
||||
pub mod error;
|
||||
pub mod node_delta;
|
||||
pub mod traversal;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use ruvector_delta_core::{Delta, DeltaStream, VectorDelta};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub use edge_delta::{EdgeDelta, EdgeOp};
|
||||
pub use error::{GraphDeltaError, Result};
|
||||
pub use node_delta::{NodeDelta, PropertyDelta};
|
||||
pub use traversal::{DeltaAwareTraversal, TraversalMode};
|
||||
|
||||
/// A node ID type
|
||||
pub type NodeId = String;
|
||||
|
||||
/// An edge ID type
|
||||
pub type EdgeId = String;
|
||||
|
||||
/// Property value types
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PropertyValue {
|
||||
/// Null/missing value
|
||||
Null,
|
||||
/// Boolean
|
||||
Bool(bool),
|
||||
/// Integer
|
||||
Int(i64),
|
||||
/// Float
|
||||
Float(f64),
|
||||
/// String
|
||||
String(String),
|
||||
/// Vector (for embeddings)
|
||||
Vector(Vec<f32>),
|
||||
/// List of values
|
||||
List(Vec<PropertyValue>),
|
||||
/// Map of values
|
||||
Map(HashMap<String, PropertyValue>),
|
||||
}
|
||||
|
||||
impl Default for PropertyValue {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
/// A graph delta representing changes to graph structure
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphDelta {
|
||||
/// Node additions
|
||||
pub node_adds: Vec<(NodeId, HashMap<String, PropertyValue>)>,
|
||||
/// Node removals
|
||||
pub node_removes: Vec<NodeId>,
|
||||
/// Node property updates
|
||||
pub node_updates: Vec<(NodeId, NodeDelta)>,
|
||||
/// Edge additions
|
||||
pub edge_adds: Vec<EdgeAddition>,
|
||||
/// Edge removals
|
||||
pub edge_removes: Vec<EdgeId>,
|
||||
/// Edge updates
|
||||
pub edge_updates: Vec<(EdgeId, EdgeDelta)>,
|
||||
}
|
||||
|
||||
/// An edge addition
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EdgeAddition {
|
||||
/// Edge ID
|
||||
pub id: EdgeId,
|
||||
/// Source node
|
||||
pub source: NodeId,
|
||||
/// Target node
|
||||
pub target: NodeId,
|
||||
/// Edge type/label
|
||||
pub edge_type: String,
|
||||
/// Edge properties
|
||||
pub properties: HashMap<String, PropertyValue>,
|
||||
}
|
||||
|
||||
impl GraphDelta {
|
||||
/// Create an empty graph delta
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
node_adds: Vec::new(),
|
||||
node_removes: Vec::new(),
|
||||
node_updates: Vec::new(),
|
||||
edge_adds: Vec::new(),
|
||||
edge_removes: Vec::new(),
|
||||
edge_updates: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if delta is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.node_adds.is_empty()
|
||||
&& self.node_removes.is_empty()
|
||||
&& self.node_updates.is_empty()
|
||||
&& self.edge_adds.is_empty()
|
||||
&& self.edge_removes.is_empty()
|
||||
&& self.edge_updates.is_empty()
|
||||
}
|
||||
|
||||
/// Get total number of operations
|
||||
pub fn operation_count(&self) -> usize {
|
||||
self.node_adds.len()
|
||||
+ self.node_removes.len()
|
||||
+ self.node_updates.len()
|
||||
+ self.edge_adds.len()
|
||||
+ self.edge_removes.len()
|
||||
+ self.edge_updates.len()
|
||||
}
|
||||
|
||||
/// Add a node addition
|
||||
pub fn add_node(&mut self, id: NodeId, properties: HashMap<String, PropertyValue>) {
|
||||
self.node_adds.push((id, properties));
|
||||
}
|
||||
|
||||
/// Add a node removal
|
||||
pub fn remove_node(&mut self, id: NodeId) {
|
||||
self.node_removes.push(id);
|
||||
}
|
||||
|
||||
/// Add a node update
|
||||
pub fn update_node(&mut self, id: NodeId, delta: NodeDelta) {
|
||||
self.node_updates.push((id, delta));
|
||||
}
|
||||
|
||||
/// Add an edge
|
||||
pub fn add_edge(&mut self, edge: EdgeAddition) {
|
||||
self.edge_adds.push(edge);
|
||||
}
|
||||
|
||||
/// Remove an edge
|
||||
pub fn remove_edge(&mut self, id: EdgeId) {
|
||||
self.edge_removes.push(id);
|
||||
}
|
||||
|
||||
/// Update an edge
|
||||
pub fn update_edge(&mut self, id: EdgeId, delta: EdgeDelta) {
|
||||
self.edge_updates.push((id, delta));
|
||||
}
|
||||
|
||||
/// Compose with another graph delta
|
||||
pub fn compose(mut self, other: GraphDelta) -> Self {
|
||||
// Handle conflicting operations
|
||||
let removed_nodes: HashSet<_> = other.node_removes.iter().collect();
|
||||
let removed_edges: HashSet<_> = other.edge_removes.iter().collect();
|
||||
|
||||
// Filter out adds that are removed
|
||||
self.node_adds.retain(|(id, _)| !removed_nodes.contains(id));
|
||||
self.edge_adds.retain(|e| !removed_edges.contains(&e.id));
|
||||
|
||||
// Merge adds
|
||||
self.node_adds.extend(other.node_adds);
|
||||
self.node_removes.extend(other.node_removes);
|
||||
self.edge_adds.extend(other.edge_adds);
|
||||
self.edge_removes.extend(other.edge_removes);
|
||||
|
||||
// Compose node updates
|
||||
let mut node_update_map: HashMap<NodeId, NodeDelta> = HashMap::new();
|
||||
for (id, delta) in self.node_updates {
|
||||
node_update_map.insert(id, delta);
|
||||
}
|
||||
for (id, delta) in other.node_updates {
|
||||
node_update_map
|
||||
.entry(id)
|
||||
.and_modify(|existing| *existing = existing.clone().compose(delta.clone()))
|
||||
.or_insert(delta);
|
||||
}
|
||||
self.node_updates = node_update_map.into_iter().collect();
|
||||
|
||||
// Compose edge updates
|
||||
let mut edge_update_map: HashMap<EdgeId, EdgeDelta> = HashMap::new();
|
||||
for (id, delta) in self.edge_updates {
|
||||
edge_update_map.insert(id, delta);
|
||||
}
|
||||
for (id, delta) in other.edge_updates {
|
||||
edge_update_map
|
||||
.entry(id)
|
||||
.and_modify(|existing| *existing = existing.clone().compose(delta.clone()))
|
||||
.or_insert(delta);
|
||||
}
|
||||
self.edge_updates = edge_update_map.into_iter().collect();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Compute inverse delta
|
||||
pub fn inverse(&self) -> Self {
|
||||
// Note: Full inverse requires knowing original state
|
||||
// This is a partial inverse that works for simple cases
|
||||
Self {
|
||||
node_adds: Vec::new(), // Would need originals to undo removes
|
||||
node_removes: self.node_adds.iter().map(|(id, _)| id.clone()).collect(),
|
||||
node_updates: self
|
||||
.node_updates
|
||||
.iter()
|
||||
.map(|(id, d)| (id.clone(), d.inverse()))
|
||||
.collect(),
|
||||
edge_adds: Vec::new(),
|
||||
edge_removes: self.edge_adds.iter().map(|e| e.id.clone()).collect(),
|
||||
edge_updates: self
|
||||
.edge_updates
|
||||
.iter()
|
||||
.map(|(id, d)| (id.clone(), d.inverse()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get affected node IDs
|
||||
pub fn affected_nodes(&self) -> HashSet<NodeId> {
|
||||
let mut nodes = HashSet::new();
|
||||
|
||||
for (id, _) in &self.node_adds {
|
||||
nodes.insert(id.clone());
|
||||
}
|
||||
for id in &self.node_removes {
|
||||
nodes.insert(id.clone());
|
||||
}
|
||||
for (id, _) in &self.node_updates {
|
||||
nodes.insert(id.clone());
|
||||
}
|
||||
for edge in &self.edge_adds {
|
||||
nodes.insert(edge.source.clone());
|
||||
nodes.insert(edge.target.clone());
|
||||
}
|
||||
|
||||
nodes
|
||||
}
|
||||
|
||||
/// Get affected edge IDs
|
||||
pub fn affected_edges(&self) -> HashSet<EdgeId> {
|
||||
let mut edges = HashSet::new();
|
||||
|
||||
for edge in &self.edge_adds {
|
||||
edges.insert(edge.id.clone());
|
||||
}
|
||||
for id in &self.edge_removes {
|
||||
edges.insert(id.clone());
|
||||
}
|
||||
for (id, _) in &self.edge_updates {
|
||||
edges.insert(id.clone());
|
||||
}
|
||||
|
||||
edges
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GraphDelta {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for graph deltas
|
||||
pub struct GraphDeltaBuilder {
|
||||
delta: GraphDelta,
|
||||
}
|
||||
|
||||
impl GraphDeltaBuilder {
|
||||
/// Create a new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
delta: GraphDelta::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a node
|
||||
pub fn add_node(mut self, id: impl Into<NodeId>) -> Self {
|
||||
self.delta.add_node(id.into(), HashMap::new());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a node with properties
|
||||
pub fn add_node_with_props(
|
||||
mut self,
|
||||
id: impl Into<NodeId>,
|
||||
props: HashMap<String, PropertyValue>,
|
||||
) -> Self {
|
||||
self.delta.add_node(id.into(), props);
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a node
|
||||
pub fn remove_node(mut self, id: impl Into<NodeId>) -> Self {
|
||||
self.delta.remove_node(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an edge
|
||||
pub fn add_edge(
|
||||
mut self,
|
||||
id: impl Into<EdgeId>,
|
||||
source: impl Into<NodeId>,
|
||||
target: impl Into<NodeId>,
|
||||
edge_type: impl Into<String>,
|
||||
) -> Self {
|
||||
self.delta.add_edge(EdgeAddition {
|
||||
id: id.into(),
|
||||
source: source.into(),
|
||||
target: target.into(),
|
||||
edge_type: edge_type.into(),
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove an edge
|
||||
pub fn remove_edge(mut self, id: impl Into<EdgeId>) -> Self {
|
||||
self.delta.remove_edge(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the delta
|
||||
pub fn build(self) -> GraphDelta {
|
||||
self.delta
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GraphDeltaBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream of graph deltas for event sourcing
|
||||
pub struct GraphDeltaStream {
|
||||
stream: DeltaStream<WrappedGraphDelta>,
|
||||
}
|
||||
|
||||
// Wrapper to implement Delta trait
|
||||
#[derive(Clone)]
|
||||
struct WrappedGraphDelta(GraphDelta);
|
||||
|
||||
impl Delta for WrappedGraphDelta {
|
||||
type Base = GraphState;
|
||||
type Error = GraphDeltaError;
|
||||
|
||||
fn compute(_old: &Self::Base, _new: &Self::Base) -> Self {
|
||||
// Would diff two graph states
|
||||
WrappedGraphDelta(GraphDelta::new())
|
||||
}
|
||||
|
||||
fn apply(&self, base: &mut Self::Base) -> std::result::Result<(), Self::Error> {
|
||||
base.apply_delta(&self.0)
|
||||
}
|
||||
|
||||
fn compose(self, other: Self) -> Self {
|
||||
WrappedGraphDelta(self.0.compose(other.0))
|
||||
}
|
||||
|
||||
fn inverse(&self) -> Self {
|
||||
WrappedGraphDelta(self.0.inverse())
|
||||
}
|
||||
|
||||
fn is_identity(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
fn byte_size(&self) -> usize {
|
||||
// Approximate size
|
||||
std::mem::size_of::<GraphDelta>() + self.0.operation_count() * 100
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WrappedGraphDelta {
|
||||
fn default() -> Self {
|
||||
Self(GraphDelta::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified graph state for delta application
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct GraphState {
|
||||
/// Nodes with properties
|
||||
pub nodes: HashMap<NodeId, HashMap<String, PropertyValue>>,
|
||||
/// Edges
|
||||
pub edges: HashMap<EdgeId, (NodeId, NodeId, String, HashMap<String, PropertyValue>)>,
|
||||
}
|
||||
|
||||
impl GraphState {
|
||||
/// Create empty state
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Apply a graph delta
|
||||
pub fn apply_delta(&mut self, delta: &GraphDelta) -> Result<()> {
|
||||
// Remove nodes
|
||||
for id in &delta.node_removes {
|
||||
self.nodes.remove(id);
|
||||
// Also remove incident edges
|
||||
self.edges.retain(|_, (s, t, _, _)| s != id && t != id);
|
||||
}
|
||||
|
||||
// Remove edges
|
||||
for id in &delta.edge_removes {
|
||||
self.edges.remove(id);
|
||||
}
|
||||
|
||||
// Add nodes
|
||||
for (id, props) in &delta.node_adds {
|
||||
self.nodes.insert(id.clone(), props.clone());
|
||||
}
|
||||
|
||||
// Add edges
|
||||
for edge in &delta.edge_adds {
|
||||
self.edges.insert(
|
||||
edge.id.clone(),
|
||||
(
|
||||
edge.source.clone(),
|
||||
edge.target.clone(),
|
||||
edge.edge_type.clone(),
|
||||
edge.properties.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Update nodes
|
||||
for (id, node_delta) in &delta.node_updates {
|
||||
if let Some(props) = self.nodes.get_mut(id) {
|
||||
for prop_delta in &node_delta.property_deltas {
|
||||
match &prop_delta.operation {
|
||||
PropertyOp::Set(value) => {
|
||||
props.insert(prop_delta.key.clone(), value.clone());
|
||||
}
|
||||
PropertyOp::Remove => {
|
||||
props.remove(&prop_delta.key);
|
||||
}
|
||||
PropertyOp::VectorDelta(vd) => {
|
||||
if let Some(PropertyValue::Vector(v)) = props.get_mut(&prop_delta.key) {
|
||||
vd.apply(v)
|
||||
.map_err(|e| GraphDeltaError::DeltaError(format!("{:?}", e)))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update edges
|
||||
for (id, edge_delta) in &delta.edge_updates {
|
||||
if let Some((_, _, _, props)) = self.edges.get_mut(id) {
|
||||
for prop_delta in &edge_delta.property_deltas {
|
||||
match &prop_delta.operation {
|
||||
PropertyOp::Set(value) => {
|
||||
props.insert(prop_delta.key.clone(), value.clone());
|
||||
}
|
||||
PropertyOp::Remove => {
|
||||
props.remove(&prop_delta.key);
|
||||
}
|
||||
PropertyOp::VectorDelta(_) => {
|
||||
// Edges typically don't have vector properties
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get node count
|
||||
pub fn node_count(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
/// Get edge count
|
||||
pub fn edge_count(&self) -> usize {
|
||||
self.edges.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Property operation types
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PropertyOp {
|
||||
/// Set property value
|
||||
Set(PropertyValue),
|
||||
/// Remove property
|
||||
Remove,
|
||||
/// Apply vector delta to vector property
|
||||
VectorDelta(VectorDelta),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_graph_delta_builder() {
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_node("a")
|
||||
.add_node("b")
|
||||
.add_edge("e1", "a", "b", "KNOWS")
|
||||
.build();
|
||||
|
||||
assert_eq!(delta.node_adds.len(), 2);
|
||||
assert_eq!(delta.edge_adds.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graph_state_apply() {
|
||||
let mut state = GraphState::new();
|
||||
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_node("a")
|
||||
.add_node("b")
|
||||
.add_edge("e1", "a", "b", "KNOWS")
|
||||
.build();
|
||||
|
||||
state.apply_delta(&delta).unwrap();
|
||||
|
||||
assert_eq!(state.node_count(), 2);
|
||||
assert_eq!(state.edge_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delta_compose() {
|
||||
let d1 = GraphDeltaBuilder::new().add_node("a").add_node("b").build();
|
||||
|
||||
let d2 = GraphDeltaBuilder::new()
|
||||
.remove_node("b")
|
||||
.add_node("c")
|
||||
.build();
|
||||
|
||||
let composed = d1.compose(d2);
|
||||
|
||||
// "a" added, "b" removed, "c" added
|
||||
assert_eq!(composed.node_adds.len(), 2); // a and c
|
||||
assert_eq!(composed.node_removes.len(), 1); // b
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_affected_nodes() {
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_node("a")
|
||||
.add_edge("e1", "b", "c", "RELATES")
|
||||
.build();
|
||||
|
||||
let affected = delta.affected_nodes();
|
||||
|
||||
assert!(affected.contains("a"));
|
||||
assert!(affected.contains("b"));
|
||||
assert!(affected.contains("c"));
|
||||
}
|
||||
}
|
||||
280
vendor/ruvector/crates/ruvector-delta-graph/src/node_delta.rs
vendored
Normal file
280
vendor/ruvector/crates/ruvector-delta-graph/src/node_delta.rs
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
//! Node delta operations
|
||||
//!
|
||||
//! Represents changes to nodes in a graph.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ruvector_delta_core::{Delta, VectorDelta};
|
||||
|
||||
use crate::{NodeId, PropertyOp, PropertyValue};
|
||||
|
||||
/// A property delta for nodes
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PropertyDelta {
|
||||
/// Property key
|
||||
pub key: String,
|
||||
/// Operation
|
||||
pub operation: PropertyOp,
|
||||
}
|
||||
|
||||
impl PropertyDelta {
|
||||
/// Create a set operation
|
||||
pub fn set(key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
operation: PropertyOp::Set(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a remove operation
|
||||
pub fn remove(key: impl Into<String>) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
operation: PropertyOp::Remove,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a vector delta operation
|
||||
pub fn vector_delta(key: impl Into<String>, delta: VectorDelta) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
operation: PropertyOp::VectorDelta(delta),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delta for a node
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeDelta {
|
||||
/// Property deltas
|
||||
pub property_deltas: Vec<PropertyDelta>,
|
||||
/// Label changes (add/remove)
|
||||
pub label_adds: Vec<String>,
|
||||
/// Labels to remove
|
||||
pub label_removes: Vec<String>,
|
||||
}
|
||||
|
||||
impl NodeDelta {
|
||||
/// Create empty delta
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
property_deltas: Vec::new(),
|
||||
label_adds: Vec::new(),
|
||||
label_removes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.property_deltas.is_empty()
|
||||
&& self.label_adds.is_empty()
|
||||
&& self.label_removes.is_empty()
|
||||
}
|
||||
|
||||
/// Add a property set
|
||||
pub fn set_property(mut self, key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
self.property_deltas.push(PropertyDelta::set(key, value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a property removal
|
||||
pub fn remove_property(mut self, key: impl Into<String>) -> Self {
|
||||
self.property_deltas.push(PropertyDelta::remove(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a vector delta for an embedding property
|
||||
pub fn vector_delta(mut self, key: impl Into<String>, delta: VectorDelta) -> Self {
|
||||
self.property_deltas
|
||||
.push(PropertyDelta::vector_delta(key, delta));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a label
|
||||
pub fn add_label(mut self, label: impl Into<String>) -> Self {
|
||||
self.label_adds.push(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a label
|
||||
pub fn remove_label(mut self, label: impl Into<String>) -> Self {
|
||||
self.label_removes.push(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Compose with another delta
|
||||
pub fn compose(mut self, other: NodeDelta) -> Self {
|
||||
// Merge property deltas (later overrides earlier)
|
||||
let mut prop_map: HashMap<String, PropertyDelta> = HashMap::new();
|
||||
for pd in self.property_deltas {
|
||||
prop_map.insert(pd.key.clone(), pd);
|
||||
}
|
||||
for pd in other.property_deltas {
|
||||
// For vector deltas, compose them
|
||||
if let PropertyOp::VectorDelta(vd2) = &pd.operation {
|
||||
if let Some(existing) = prop_map.get(&pd.key) {
|
||||
if let PropertyOp::VectorDelta(vd1) = &existing.operation {
|
||||
let composed = vd1.clone().compose(vd2.clone());
|
||||
prop_map.insert(
|
||||
pd.key.clone(),
|
||||
PropertyDelta::vector_delta(pd.key.clone(), composed),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
prop_map.insert(pd.key.clone(), pd);
|
||||
}
|
||||
self.property_deltas = prop_map.into_values().collect();
|
||||
|
||||
// Merge label changes
|
||||
let mut adds: std::collections::HashSet<String> = self.label_adds.into_iter().collect();
|
||||
let mut removes: std::collections::HashSet<String> =
|
||||
self.label_removes.into_iter().collect();
|
||||
|
||||
for label in other.label_adds {
|
||||
removes.remove(&label);
|
||||
adds.insert(label);
|
||||
}
|
||||
for label in other.label_removes {
|
||||
adds.remove(&label);
|
||||
removes.insert(label);
|
||||
}
|
||||
|
||||
self.label_adds = adds.into_iter().collect();
|
||||
self.label_removes = removes.into_iter().collect();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Compute inverse
|
||||
pub fn inverse(&self) -> Self {
|
||||
// Swap adds and removes for labels
|
||||
// Property deltas can't be fully inverted without originals
|
||||
Self {
|
||||
property_deltas: Vec::new(),
|
||||
label_adds: self.label_removes.clone(),
|
||||
label_removes: self.label_adds.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeDelta {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for node deltas
|
||||
pub struct NodeDeltaBuilder {
|
||||
delta: NodeDelta,
|
||||
}
|
||||
|
||||
impl NodeDeltaBuilder {
|
||||
/// Create new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
delta: NodeDelta::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a property
|
||||
pub fn set(mut self, key: impl Into<String>, value: PropertyValue) -> Self {
|
||||
self.delta
|
||||
.property_deltas
|
||||
.push(PropertyDelta::set(key, value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a property
|
||||
pub fn remove(mut self, key: impl Into<String>) -> Self {
|
||||
self.delta.property_deltas.push(PropertyDelta::remove(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Apply vector delta
|
||||
pub fn vector(mut self, key: impl Into<String>, delta: VectorDelta) -> Self {
|
||||
self.delta
|
||||
.property_deltas
|
||||
.push(PropertyDelta::vector_delta(key, delta));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a label
|
||||
pub fn add_label(mut self, label: impl Into<String>) -> Self {
|
||||
self.delta.label_adds.push(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove a label
|
||||
pub fn remove_label(mut self, label: impl Into<String>) -> Self {
|
||||
self.delta.label_removes.push(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the delta
|
||||
pub fn build(self) -> NodeDelta {
|
||||
self.delta
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeDeltaBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_node_delta_builder() {
|
||||
let delta = NodeDeltaBuilder::new()
|
||||
.set("name", PropertyValue::String("Alice".into()))
|
||||
.set("age", PropertyValue::Int(30))
|
||||
.add_label("Person")
|
||||
.build();
|
||||
|
||||
assert_eq!(delta.property_deltas.len(), 2);
|
||||
assert_eq!(delta.label_adds.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_delta_compose() {
|
||||
let d1 = NodeDelta::new()
|
||||
.set_property("a", PropertyValue::Int(1))
|
||||
.add_label("Label1");
|
||||
|
||||
let d2 = NodeDelta::new()
|
||||
.set_property("b", PropertyValue::Int(2))
|
||||
.remove_label("Label1")
|
||||
.add_label("Label2");
|
||||
|
||||
let composed = d1.compose(d2);
|
||||
|
||||
assert_eq!(composed.property_deltas.len(), 2);
|
||||
assert!(composed.label_adds.contains(&"Label2".to_string()));
|
||||
assert!(!composed.label_adds.contains(&"Label1".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_delta_compose() {
|
||||
let vd1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
|
||||
let vd2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
|
||||
|
||||
let d1 = NodeDelta::new().vector_delta("embedding", vd1);
|
||||
let d2 = NodeDelta::new().vector_delta("embedding", vd2);
|
||||
|
||||
let composed = d1.compose(d2);
|
||||
|
||||
assert_eq!(composed.property_deltas.len(), 1);
|
||||
|
||||
if let PropertyOp::VectorDelta(vd) = &composed.property_deltas[0].operation {
|
||||
// Should be composed delta
|
||||
assert!(!vd.is_identity());
|
||||
} else {
|
||||
panic!("Expected vector delta");
|
||||
}
|
||||
}
|
||||
}
|
||||
452
vendor/ruvector/crates/ruvector-delta-graph/src/traversal.rs
vendored
Normal file
452
vendor/ruvector/crates/ruvector-delta-graph/src/traversal.rs
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
//! Delta-aware graph traversal
|
||||
//!
|
||||
//! Provides traversal algorithms that account for pending deltas.
|
||||
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
use crate::{EdgeId, GraphDelta, GraphState, NodeId, Result};
|
||||
|
||||
/// Traversal mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TraversalMode {
|
||||
/// Breadth-first search
|
||||
Bfs,
|
||||
/// Depth-first search
|
||||
Dfs,
|
||||
/// Dijkstra's shortest path
|
||||
Dijkstra,
|
||||
/// Best-first (with heuristic)
|
||||
BestFirst,
|
||||
}
|
||||
|
||||
/// Configuration for delta-aware traversal
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TraversalConfig {
|
||||
/// Maximum depth
|
||||
pub max_depth: usize,
|
||||
/// Maximum nodes to visit
|
||||
pub max_nodes: usize,
|
||||
/// Edge types to follow (empty = all)
|
||||
pub edge_types: Vec<String>,
|
||||
/// Direction
|
||||
pub direction: TraversalDirection,
|
||||
}
|
||||
|
||||
impl Default for TraversalConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_depth: usize::MAX,
|
||||
max_nodes: usize::MAX,
|
||||
edge_types: Vec::new(),
|
||||
direction: TraversalDirection::Outgoing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Traversal direction
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TraversalDirection {
|
||||
/// Follow outgoing edges
|
||||
Outgoing,
|
||||
/// Follow incoming edges
|
||||
Incoming,
|
||||
/// Follow both directions
|
||||
Both,
|
||||
}
|
||||
|
||||
/// Delta-aware graph traversal
|
||||
pub struct DeltaAwareTraversal<'a> {
|
||||
state: &'a GraphState,
|
||||
pending_delta: Option<&'a GraphDelta>,
|
||||
config: TraversalConfig,
|
||||
}
|
||||
|
||||
impl<'a> DeltaAwareTraversal<'a> {
|
||||
/// Create new traversal
|
||||
pub fn new(state: &'a GraphState) -> Self {
|
||||
Self {
|
||||
state,
|
||||
pending_delta: None,
|
||||
config: TraversalConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set pending delta to consider
|
||||
pub fn with_delta(mut self, delta: &'a GraphDelta) -> Self {
|
||||
self.pending_delta = Some(delta);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set configuration
|
||||
pub fn with_config(mut self, config: TraversalConfig) -> Self {
|
||||
self.config = config;
|
||||
self
|
||||
}
|
||||
|
||||
/// BFS from start node
|
||||
pub fn bfs(&self, start: &NodeId) -> Vec<NodeId> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut queue = VecDeque::new();
|
||||
let mut result = Vec::new();
|
||||
|
||||
if !self.node_exists(start) {
|
||||
return result;
|
||||
}
|
||||
|
||||
queue.push_back((start.clone(), 0usize));
|
||||
visited.insert(start.clone());
|
||||
|
||||
while let Some((node, depth)) = queue.pop_front() {
|
||||
if depth > self.config.max_depth {
|
||||
continue;
|
||||
}
|
||||
if result.len() >= self.config.max_nodes {
|
||||
break;
|
||||
}
|
||||
|
||||
result.push(node.clone());
|
||||
|
||||
for neighbor in self.neighbors(&node) {
|
||||
if !visited.contains(&neighbor) {
|
||||
visited.insert(neighbor.clone());
|
||||
queue.push_back((neighbor, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// DFS from start node
|
||||
pub fn dfs(&self, start: &NodeId) -> Vec<NodeId> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut stack = Vec::new();
|
||||
let mut result = Vec::new();
|
||||
|
||||
if !self.node_exists(start) {
|
||||
return result;
|
||||
}
|
||||
|
||||
stack.push((start.clone(), 0usize));
|
||||
|
||||
while let Some((node, depth)) = stack.pop() {
|
||||
if visited.contains(&node) {
|
||||
continue;
|
||||
}
|
||||
if depth > self.config.max_depth {
|
||||
continue;
|
||||
}
|
||||
if result.len() >= self.config.max_nodes {
|
||||
break;
|
||||
}
|
||||
|
||||
visited.insert(node.clone());
|
||||
result.push(node.clone());
|
||||
|
||||
for neighbor in self.neighbors(&node) {
|
||||
if !visited.contains(&neighbor) {
|
||||
stack.push((neighbor, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Find shortest path
|
||||
pub fn shortest_path(&self, from: &NodeId, to: &NodeId) -> Option<Vec<NodeId>> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut queue = VecDeque::new();
|
||||
let mut parent: HashMap<NodeId, NodeId> = HashMap::new();
|
||||
|
||||
if !self.node_exists(from) || !self.node_exists(to) {
|
||||
return None;
|
||||
}
|
||||
|
||||
queue.push_back(from.clone());
|
||||
visited.insert(from.clone());
|
||||
|
||||
while let Some(node) = queue.pop_front() {
|
||||
if &node == to {
|
||||
// Reconstruct path
|
||||
let mut path = vec![node.clone()];
|
||||
let mut current = &node;
|
||||
while let Some(p) = parent.get(current) {
|
||||
path.push(p.clone());
|
||||
current = p;
|
||||
}
|
||||
path.reverse();
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
for neighbor in self.neighbors(&node) {
|
||||
if !visited.contains(&neighbor) {
|
||||
visited.insert(neighbor.clone());
|
||||
parent.insert(neighbor.clone(), node.clone());
|
||||
queue.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Count connected components
|
||||
pub fn connected_components(&self) -> Vec<Vec<NodeId>> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut components = Vec::new();
|
||||
|
||||
let all_nodes = self.all_nodes();
|
||||
|
||||
for node in all_nodes {
|
||||
if !visited.contains(&node) {
|
||||
// Use BFS to find component
|
||||
let mut component = Vec::new();
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(node.clone());
|
||||
visited.insert(node.clone());
|
||||
|
||||
while let Some(n) = queue.pop_front() {
|
||||
component.push(n.clone());
|
||||
|
||||
for neighbor in self.neighbors_both_directions(&n) {
|
||||
if !visited.contains(&neighbor) {
|
||||
visited.insert(neighbor.clone());
|
||||
queue.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
components
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
fn node_exists(&self, id: &NodeId) -> bool {
|
||||
// Check base state
|
||||
if self.state.nodes.contains_key(id) {
|
||||
// Check if removed by delta
|
||||
if let Some(delta) = self.pending_delta {
|
||||
if delta.node_removes.contains(id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if added by delta
|
||||
if let Some(delta) = self.pending_delta {
|
||||
if delta.node_adds.iter().any(|(nid, _)| nid == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn neighbors(&self, id: &NodeId) -> Vec<NodeId> {
|
||||
let mut neighbors = Vec::new();
|
||||
|
||||
// Get neighbors from base state
|
||||
for (edge_id, (source, target, edge_type, _)) in &self.state.edges {
|
||||
// Skip if removed by delta
|
||||
if let Some(delta) = self.pending_delta {
|
||||
if delta.edge_removes.contains(edge_id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by edge type
|
||||
if !self.config.edge_types.is_empty() && !self.config.edge_types.contains(edge_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match self.config.direction {
|
||||
TraversalDirection::Outgoing => {
|
||||
if source == id {
|
||||
neighbors.push(target.clone());
|
||||
}
|
||||
}
|
||||
TraversalDirection::Incoming => {
|
||||
if target == id {
|
||||
neighbors.push(source.clone());
|
||||
}
|
||||
}
|
||||
TraversalDirection::Both => {
|
||||
if source == id {
|
||||
neighbors.push(target.clone());
|
||||
} else if target == id {
|
||||
neighbors.push(source.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add neighbors from delta edges
|
||||
if let Some(delta) = self.pending_delta {
|
||||
for edge in &delta.edge_adds {
|
||||
if !self.config.edge_types.is_empty()
|
||||
&& !self.config.edge_types.contains(&edge.edge_type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match self.config.direction {
|
||||
TraversalDirection::Outgoing => {
|
||||
if &edge.source == id {
|
||||
neighbors.push(edge.target.clone());
|
||||
}
|
||||
}
|
||||
TraversalDirection::Incoming => {
|
||||
if &edge.target == id {
|
||||
neighbors.push(edge.source.clone());
|
||||
}
|
||||
}
|
||||
TraversalDirection::Both => {
|
||||
if &edge.source == id {
|
||||
neighbors.push(edge.target.clone());
|
||||
} else if &edge.target == id {
|
||||
neighbors.push(edge.source.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
neighbors
|
||||
}
|
||||
|
||||
fn neighbors_both_directions(&self, id: &NodeId) -> Vec<NodeId> {
|
||||
let config = TraversalConfig {
|
||||
direction: TraversalDirection::Both,
|
||||
..self.config.clone()
|
||||
};
|
||||
|
||||
let traversal = DeltaAwareTraversal {
|
||||
state: self.state,
|
||||
pending_delta: self.pending_delta,
|
||||
config,
|
||||
};
|
||||
|
||||
traversal.neighbors(id)
|
||||
}
|
||||
|
||||
fn all_nodes(&self) -> Vec<NodeId> {
|
||||
let mut nodes: HashSet<NodeId> = self.state.nodes.keys().cloned().collect();
|
||||
|
||||
if let Some(delta) = self.pending_delta {
|
||||
// Remove deleted nodes
|
||||
for id in &delta.node_removes {
|
||||
nodes.remove(id);
|
||||
}
|
||||
|
||||
// Add new nodes
|
||||
for (id, _) in &delta.node_adds {
|
||||
nodes.insert(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
nodes.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::GraphDeltaBuilder;
|
||||
|
||||
fn create_test_state() -> GraphState {
|
||||
let mut state = GraphState::new();
|
||||
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_node("a")
|
||||
.add_node("b")
|
||||
.add_node("c")
|
||||
.add_node("d")
|
||||
.add_edge("e1", "a", "b", "KNOWS")
|
||||
.add_edge("e2", "b", "c", "KNOWS")
|
||||
.add_edge("e3", "c", "d", "KNOWS")
|
||||
.build();
|
||||
|
||||
state.apply_delta(&delta).unwrap();
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bfs() {
|
||||
let state = create_test_state();
|
||||
let traversal = DeltaAwareTraversal::new(&state);
|
||||
|
||||
let result = traversal.bfs(&"a".to_string());
|
||||
|
||||
assert_eq!(result.len(), 4);
|
||||
assert_eq!(result[0], "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dfs() {
|
||||
let state = create_test_state();
|
||||
let traversal = DeltaAwareTraversal::new(&state);
|
||||
|
||||
let result = traversal.dfs(&"a".to_string());
|
||||
|
||||
assert_eq!(result.len(), 4);
|
||||
assert_eq!(result[0], "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shortest_path() {
|
||||
let state = create_test_state();
|
||||
let traversal = DeltaAwareTraversal::new(&state);
|
||||
|
||||
let path = traversal
|
||||
.shortest_path(&"a".to_string(), &"d".to_string())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(path.len(), 4);
|
||||
assert_eq!(path[0], "a");
|
||||
assert_eq!(path[3], "d");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_pending_delta() {
|
||||
let state = create_test_state();
|
||||
|
||||
// Add new edge directly from a to d
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_edge("e4", "a", "d", "SHORTCUT")
|
||||
.build();
|
||||
|
||||
let traversal = DeltaAwareTraversal::new(&state).with_delta(&delta);
|
||||
|
||||
let path = traversal
|
||||
.shortest_path(&"a".to_string(), &"d".to_string())
|
||||
.unwrap();
|
||||
|
||||
// Should find shorter path via new edge
|
||||
assert_eq!(path.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connected_components() {
|
||||
let mut state = create_test_state();
|
||||
|
||||
// Add isolated nodes
|
||||
let delta = GraphDeltaBuilder::new()
|
||||
.add_node("x")
|
||||
.add_node("y")
|
||||
.add_edge("ex", "x", "y", "RELATED")
|
||||
.build();
|
||||
|
||||
state.apply_delta(&delta).unwrap();
|
||||
|
||||
let traversal = DeltaAwareTraversal::new(&state);
|
||||
let components = traversal.connected_components();
|
||||
|
||||
assert_eq!(components.len(), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user