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(EdgeId::new(1), NodeId::new(10), NodeId::new(20), "KNOWS");
159
160        assert_eq!(edge.id, EdgeId::new(1));
161        assert_eq!(edge.src, NodeId::new(10));
162        assert_eq!(edge.dst, NodeId::new(20));
163        assert_eq!(edge.edge_type.as_ref(), "KNOWS");
164    }
165
166    #[test]
167    fn test_edge_properties() {
168        let mut edge = Edge::new(EdgeId::new(1), NodeId::new(10), NodeId::new(20), "KNOWS");
169
170        edge.set_property("since", 2020i64);
171        edge.set_property("weight", 1.5f64);
172
173        assert_eq!(
174            edge.get_property("since").and_then(|v| v.as_int64()),
175            Some(2020)
176        );
177        assert_eq!(
178            edge.get_property("weight").and_then(|v| v.as_float64()),
179            Some(1.5)
180        );
181    }
182
183    #[test]
184    fn test_edge_other_endpoint() {
185        let edge = Edge::new(EdgeId::new(1), NodeId::new(10), NodeId::new(20), "KNOWS");
186
187        assert_eq!(edge.other_endpoint(NodeId::new(10)), Some(NodeId::new(20)));
188        assert_eq!(edge.other_endpoint(NodeId::new(20)), Some(NodeId::new(10)));
189        assert_eq!(edge.other_endpoint(NodeId::new(30)), None);
190    }
191
192    #[test]
193    fn test_edge_record_flags() {
194        let mut record = EdgeRecord::new(
195            EdgeId::new(1),
196            NodeId::new(10),
197            NodeId::new(20),
198            0,
199            EpochId::INITIAL,
200        );
201
202        assert!(!record.is_deleted());
203        record.set_deleted(true);
204        assert!(record.is_deleted());
205    }
206
207    #[test]
208    fn test_edge_record_size() {
209        // EdgeRecord should be a reasonable size for cache efficiency
210        let size = std::mem::size_of::<EdgeRecord>();
211        // Should be <= 64 bytes (one cache line)
212        assert!(size <= 64, "EdgeRecord is {} bytes", size);
213    }
214}