Skip to main content

graphos_core/graph/lpg/
edge.rs

1//! Edge types for the LPG model.
2
3use graphos_common::types::{EdgeId, EpochId, NodeId, PropertyKey, Value};
4use std::collections::BTreeMap;
5use std::sync::Arc;
6
7/// An edge in the labeled property graph.
8///
9/// This is the high-level representation of an edge with all its data.
10#[derive(Debug, Clone)]
11pub struct Edge {
12    /// Unique identifier.
13    pub id: EdgeId,
14    /// Source node ID.
15    pub src: NodeId,
16    /// Destination node ID.
17    pub dst: NodeId,
18    /// Edge type/label.
19    pub edge_type: Arc<str>,
20    /// Properties stored on this edge.
21    pub properties: BTreeMap<PropertyKey, Value>,
22}
23
24impl Edge {
25    /// Creates a new edge.
26    #[must_use]
27    pub fn new(id: EdgeId, src: NodeId, dst: NodeId, edge_type: impl Into<Arc<str>>) -> Self {
28        Self {
29            id,
30            src,
31            dst,
32            edge_type: edge_type.into(),
33            properties: BTreeMap::new(),
34        }
35    }
36
37    /// Sets a property on this edge.
38    pub fn set_property(&mut self, key: impl Into<PropertyKey>, value: impl Into<Value>) {
39        self.properties.insert(key.into(), value.into());
40    }
41
42    /// Gets a property from this edge.
43    #[must_use]
44    pub fn get_property(&self, key: &str) -> Option<&Value> {
45        self.properties.get(&PropertyKey::new(key))
46    }
47
48    /// Removes a property from this edge.
49    pub fn remove_property(&mut self, key: &str) -> Option<Value> {
50        self.properties.remove(&PropertyKey::new(key))
51    }
52
53    /// Returns the other endpoint of this edge given one endpoint.
54    ///
55    /// Returns `None` if `node` is neither the source nor destination.
56    #[must_use]
57    pub fn other_endpoint(&self, node: NodeId) -> Option<NodeId> {
58        if node == self.src {
59            Some(self.dst)
60        } else if node == self.dst {
61            Some(self.src)
62        } else {
63            None
64        }
65    }
66}
67
68/// The compact representation of an edge.
69///
70/// This struct is used for edge storage with minimal overhead.
71#[repr(C)]
72#[derive(Debug, Clone, Copy)]
73pub struct EdgeRecord {
74    /// Unique edge identifier.
75    pub id: EdgeId,
76    /// Source node ID.
77    pub src: NodeId,
78    /// Destination node ID.
79    pub dst: NodeId,
80    /// Edge type ID (index into type table).
81    pub type_id: u32,
82    /// Offset into the property arena.
83    pub props_offset: u32,
84    /// Number of properties.
85    pub props_count: u16,
86    /// Flags (deleted, has_version, etc.).
87    pub flags: EdgeFlags,
88    /// Epoch this record was created in.
89    pub epoch: EpochId,
90}
91
92impl EdgeRecord {
93    /// Flag indicating the edge is deleted.
94    pub const FLAG_DELETED: u16 = 1 << 0;
95    /// Flag indicating the edge has version history.
96    pub const FLAG_HAS_VERSION: u16 = 1 << 1;
97
98    /// Creates a new edge record.
99    #[must_use]
100    pub const fn new(id: EdgeId, src: NodeId, dst: NodeId, type_id: u32, epoch: EpochId) -> Self {
101        Self {
102            id,
103            src,
104            dst,
105            type_id,
106            props_offset: 0,
107            props_count: 0,
108            flags: EdgeFlags(0),
109            epoch,
110        }
111    }
112
113    /// Checks if this edge is deleted.
114    #[must_use]
115    pub const fn is_deleted(&self) -> bool {
116        self.flags.contains(Self::FLAG_DELETED)
117    }
118
119    /// Marks this edge as deleted.
120    pub fn set_deleted(&mut self, deleted: bool) {
121        if deleted {
122            self.flags.set(Self::FLAG_DELETED);
123        } else {
124            self.flags.clear(Self::FLAG_DELETED);
125        }
126    }
127}
128
129/// Flags for an edge record.
130#[repr(transparent)]
131#[derive(Debug, Clone, Copy, Default)]
132pub struct EdgeFlags(pub u16);
133
134impl EdgeFlags {
135    /// Checks if a flag is set.
136    #[must_use]
137    pub const fn contains(&self, flag: u16) -> bool {
138        (self.0 & flag) != 0
139    }
140
141    /// Sets a flag.
142    pub fn set(&mut self, flag: u16) {
143        self.0 |= flag;
144    }
145
146    /// Clears a flag.
147    pub fn clear(&mut self, flag: u16) {
148        self.0 &= !flag;
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_edge_creation() {
158        let edge = Edge::new(
159            EdgeId::new(1),
160            NodeId::new(10),
161            NodeId::new(20),
162            "KNOWS",
163        );
164
165        assert_eq!(edge.id, EdgeId::new(1));
166        assert_eq!(edge.src, NodeId::new(10));
167        assert_eq!(edge.dst, NodeId::new(20));
168        assert_eq!(edge.edge_type.as_ref(), "KNOWS");
169    }
170
171    #[test]
172    fn test_edge_properties() {
173        let mut edge = Edge::new(
174            EdgeId::new(1),
175            NodeId::new(10),
176            NodeId::new(20),
177            "KNOWS",
178        );
179
180        edge.set_property("since", 2020i64);
181        edge.set_property("weight", 1.5f64);
182
183        assert_eq!(edge.get_property("since").and_then(|v| v.as_int64()), Some(2020));
184        assert_eq!(edge.get_property("weight").and_then(|v| v.as_float64()), Some(1.5));
185    }
186
187    #[test]
188    fn test_edge_other_endpoint() {
189        let edge = Edge::new(
190            EdgeId::new(1),
191            NodeId::new(10),
192            NodeId::new(20),
193            "KNOWS",
194        );
195
196        assert_eq!(edge.other_endpoint(NodeId::new(10)), Some(NodeId::new(20)));
197        assert_eq!(edge.other_endpoint(NodeId::new(20)), Some(NodeId::new(10)));
198        assert_eq!(edge.other_endpoint(NodeId::new(30)), None);
199    }
200
201    #[test]
202    fn test_edge_record_flags() {
203        let mut record = EdgeRecord::new(
204            EdgeId::new(1),
205            NodeId::new(10),
206            NodeId::new(20),
207            0,
208            EpochId::INITIAL,
209        );
210
211        assert!(!record.is_deleted());
212        record.set_deleted(true);
213        assert!(record.is_deleted());
214    }
215
216    #[test]
217    fn test_edge_record_size() {
218        // EdgeRecord should be a reasonable size for cache efficiency
219        let size = std::mem::size_of::<EdgeRecord>();
220        // Should be <= 64 bytes (one cache line)
221        assert!(size <= 64, "EdgeRecord is {} bytes", size);
222    }
223}