Skip to main content

linkrs_core/
vertex_edge_path.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::types::VertexId;
6use crate::value::Value;
7
8/// Represents a tag in the graph: one label with its property map.
9#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
10pub struct Tag {
11    pub name: String,
12    pub properties: HashMap<Arc<str>, Value>,
13}
14
15// Implement Hash manually for Tag to handle HashMap hashing
16impl std::hash::Hash for Tag {
17    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
18        self.name.hash(state);
19        // For HashMap, we'll hash key-value pairs in sorted order
20        let mut pairs: Vec<_> = self.properties.iter().collect();
21        pairs.sort_by_key(|&(k, _)| k);
22        for (k, v) in pairs {
23            k.hash(state);
24            v.hash(state);
25        }
26    }
27}
28
29impl Tag {
30    pub fn new(name: String, properties: HashMap<std::sync::Arc<str>, Value>) -> Self {
31        Self { name, properties }
32    }
33
34    /// Estimate the memory usage of the label.
35    pub fn estimated_size(&self) -> usize {
36        let mut size = std::mem::size_of::<Self>();
37
38        // Calculate the actual size of the variable `name` (including heap allocation).
39        size += std::mem::size_of::<String>() + self.name.capacity();
40
41        // Hash table bucket array overhead: u64 hash per entry
42        size += self.properties.capacity()
43            * (8 + std::mem::size_of::<String>() + std::mem::size_of::<Value>());
44
45        for (k, v) in &self.properties {
46            size += k.len();
47            size += v.estimated_size();
48        }
49
50        size
51    }
52}
53
54/// Represents a vertex in the graph: exactly one label with its properties.
55///
56/// Single-label is enforced by construction: there is no tag list, no
57/// vertex-level property map, and no internal row id. A vertex always carries
58/// precisely one tag.
59#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
60pub struct Vertex {
61    pub vid: VertexId,
62    pub tag: Tag,
63}
64
65// Implementing a hash function manually to handle the hashing of a HashMap
66impl std::hash::Hash for Vertex {
67    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
68        self.vid.hash(state);
69        self.tag.hash(state);
70    }
71}
72
73impl Vertex {
74    pub fn new(vid: VertexId, tag: Tag) -> Self {
75        Self { vid, tag }
76    }
77
78    pub fn vid(&self) -> &VertexId {
79        &self.vid
80    }
81
82    pub fn tag(&self) -> &Tag {
83        &self.tag
84    }
85
86    pub fn tag_name(&self) -> &str {
87        &self.tag.name
88    }
89
90    pub fn properties(&self) -> &HashMap<Arc<str>, Value> {
91        &self.tag.properties
92    }
93
94    pub fn get_property(&self, prop_name: &str) -> Option<&Value> {
95        self.tag.properties.get(prop_name)
96    }
97
98    pub fn property_value(&self, property: &str) -> Option<Value> {
99        self.tag.properties.get(property).cloned()
100    }
101
102    pub fn has_properties(&self) -> bool {
103        !self.tag.properties.is_empty()
104    }
105
106    fn cmp_properties(
107        a: &HashMap<Arc<str>, Value>,
108        b: &HashMap<Arc<str>, Value>,
109    ) -> std::cmp::Ordering {
110        match a.len().cmp(&b.len()) {
111            std::cmp::Ordering::Equal => {
112                let mut a_sorted: Vec<_> = a.iter().collect();
113                let mut b_sorted: Vec<_> = b.iter().collect();
114                a_sorted.sort_by_key(|(k1, _)| *k1);
115                b_sorted.sort_by_key(|(k1, _)| *k1);
116
117                for ((k1, v1), (k2, v2)) in a_sorted.iter().zip(b_sorted.iter()) {
118                    match k1.cmp(k2) {
119                        std::cmp::Ordering::Equal => match v1.cmp(v2) {
120                            std::cmp::Ordering::Equal => continue,
121                            ord => return ord,
122                        },
123                        ord => return ord,
124                    }
125                }
126                std::cmp::Ordering::Equal
127            }
128            ord => ord,
129        }
130    }
131}
132
133impl Ord for Vertex {
134    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
135        // Use chained comparisons to improve readability.
136        self.vid.cmp(&other.vid).then_with(|| {
137            self.tag
138                .name
139                .cmp(&other.tag.name)
140                .then_with(|| Vertex::cmp_properties(&self.tag.properties, &other.tag.properties))
141        })
142    }
143}
144
145impl PartialOrd for Vertex {
146    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
147        Some(self.cmp(other))
148    }
149}
150
151impl Vertex {
152    pub fn estimated_size(&self) -> usize {
153        let mut size = std::mem::size_of::<Self>();
154
155        size += self.vid.len();
156
157        size += self.tag.estimated_size();
158
159        size
160    }
161}
162
163/// Represents an edge in the graph, similar to Nebula's Edge structure
164///
165/// Edges are uniquely identified by (src, dst, edge_type, ranking).
166/// The internal edge ID is managed by the storage layer and is not exposed to users.
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168pub struct Edge {
169    pub src: VertexId,
170    pub dst: VertexId,
171    pub edge_type: String,
172    pub ranking: i64,
173    pub props: HashMap<Arc<str>, Value>,
174}
175
176impl Edge {
177    pub fn properties(&self) -> &HashMap<Arc<str>, Value> {
178        &self.props
179    }
180
181    pub fn src(&self) -> &VertexId {
182        &self.src
183    }
184
185    pub fn dst(&self) -> &VertexId {
186        &self.dst
187    }
188
189    pub fn edge_type(&self) -> &str {
190        &self.edge_type
191    }
192
193    pub fn ranking(&self) -> i64 {
194        self.ranking
195    }
196
197    pub fn get_all_properties(&self) -> &HashMap<Arc<str>, Value> {
198        &self.props
199    }
200
201    /// Get a specific property by name
202    pub fn get_property(&self, name: &str) -> Option<&Value> {
203        self.props.get(name)
204    }
205
206    /// Set a property value
207    pub fn set_property(&mut self, name: Arc<str>, value: Value) {
208        self.props.insert(name, value);
209    }
210
211    /// Remove a property
212    pub fn remove_property(&mut self, name: &str) -> Option<Value> {
213        self.props.remove(name)
214    }
215
216    /// Check if edge has a specific property
217    pub fn has_property(&self, name: &str) -> bool {
218        self.props.contains_key(name)
219    }
220
221    /// Get the number of properties
222    pub fn property_count(&self) -> usize {
223        self.props.len()
224    }
225
226    /// Check if edge has any properties
227    pub fn has_properties(&self) -> bool {
228        !self.props.is_empty()
229    }
230
231    pub fn new_empty(src: VertexId, dst: VertexId, edge_type: String, ranking: i64) -> Self {
232        Self {
233            src,
234            dst,
235            edge_type,
236            ranking,
237            props: HashMap::new(),
238        }
239    }
240
241    pub fn debug_string(&self) -> String {
242        format!(
243            "Edge({:?} -> {:?}, type: {}, ranking: {})",
244            self.src, self.dst, self.edge_type, self.ranking
245        )
246    }
247
248    pub fn estimated_size(&self) -> usize {
249        let mut size = std::mem::size_of::<Self>();
250
251        size += self.src.len();
252        size += self.dst.len();
253
254        size += std::mem::size_of::<String>() + self.edge_type.capacity();
255
256        size += self.props.capacity()
257            * (8 + std::mem::size_of::<Arc<str>>() + std::mem::size_of::<Value>());
258
259        for (k, v) in &self.props {
260            size += k.len();
261            size += v.estimated_size();
262        }
263
264        size
265    }
266}
267
268// Implement Hash manually for Edge to handle HashMap hashing
269impl std::hash::Hash for Edge {
270    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
271        self.src.hash(state);
272        self.dst.hash(state);
273        self.edge_type.hash(state);
274        self.ranking.hash(state);
275        // For HashMap, we'll hash key-value pairs in sorted order
276        let mut pairs: Vec<_> = self.props.iter().collect();
277        pairs.sort_by_key(|&(k, _)| k);
278        for (k, v) in pairs {
279            k.hash(state);
280            v.hash(state);
281        }
282    }
283}
284
285impl Edge {
286    pub fn new(
287        src: VertexId,
288        dst: VertexId,
289        edge_type: String,
290        ranking: i64,
291        props: HashMap<Arc<str>, Value>,
292    ) -> Self {
293        Self {
294            src,
295            dst,
296            edge_type,
297            ranking,
298            props,
299        }
300    }
301}
302
303impl Edge {
304    fn cmp_properties(
305        a: &HashMap<Arc<str>, Value>,
306        b: &HashMap<Arc<str>, Value>,
307    ) -> std::cmp::Ordering {
308        match a.len().cmp(&b.len()) {
309            std::cmp::Ordering::Equal => {
310                let mut a_sorted: Vec<_> = a.iter().collect();
311                let mut b_sorted: Vec<_> = b.iter().collect();
312                a_sorted.sort_by_key(|(k1, _)| *k1);
313                b_sorted.sort_by_key(|(k1, _)| *k1);
314
315                for ((k1, v1), (k2, v2)) in a_sorted.iter().zip(b_sorted.iter()) {
316                    match k1.cmp(k2) {
317                        std::cmp::Ordering::Equal => match v1.cmp(v2) {
318                            std::cmp::Ordering::Equal => continue,
319                            ord => return ord,
320                        },
321                        ord => return ord,
322                    }
323                }
324                std::cmp::Ordering::Equal
325            }
326            ord => ord,
327        }
328    }
329}
330
331impl Ord for Edge {
332    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
333        // Use chained comparisons to improve readability.
334        self.src
335            .cmp(&other.src)
336            .then_with(|| self.dst.cmp(&other.dst))
337            .then_with(|| self.edge_type.cmp(&other.edge_type))
338            .then_with(|| self.ranking.cmp(&other.ranking))
339            .then_with(|| Edge::cmp_properties(&self.props, &other.props))
340    }
341}
342
343impl PartialOrd for Edge {
344    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
345        Some(self.cmp(other))
346    }
347}
348
349/// Key of one edge for batch deletion, mirroring the identity half of `Edge.
350///
351/// Carries no properties: deletes address `(src, dst, edge_type, ranking)`
352/// only, so callers deleting many edges avoid building full payload edges.
353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
354pub struct EdgeDeleteKey {
355    pub src: VertexId,
356    pub dst: VertexId,
357    pub edge_type: String,
358    pub ranking: i64,
359}
360
361impl EdgeDeleteKey {
362    pub fn new(src: VertexId, dst: VertexId, edge_type: String, ranking: i64) -> Self {
363        Self {
364            src,
365            dst,
366            edge_type,
367            ranking,
368        }
369    }
370}
371
372/// Lightweight read-only view of the query-visible edge identity.
373///
374/// Carries the `(src, dst, edge_type, ranking)` tuple with no property map
375/// and no storage-internal table-scoped identifier. Scan sources emit this
376/// when the planner proves no downstream consumer needs the boxed entity;
377/// any property or endpoint-label resolution against it is a planner
378/// miss and must surface as an explicit error, never a silent null.
379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
380pub struct EdgeHeader {
381    pub src: VertexId,
382    pub dst: VertexId,
383    pub edge_type: String,
384    pub ranking: i64,
385}
386
387impl EdgeHeader {
388    pub fn new(src: VertexId, dst: VertexId, edge_type: String, ranking: i64) -> Self {
389        Self {
390            src,
391            dst,
392            edge_type,
393            ranking,
394        }
395    }
396
397    pub fn from_edge(edge: &Edge) -> Self {
398        Self {
399            src: edge.src,
400            dst: edge.dst,
401            edge_type: edge.edge_type.clone(),
402            ranking: edge.ranking,
403        }
404    }
405
406    pub fn src(&self) -> &VertexId {
407        &self.src
408    }
409
410    pub fn dst(&self) -> &VertexId {
411        &self.dst
412    }
413
414    pub fn edge_type(&self) -> &str {
415        &self.edge_type
416    }
417
418    pub fn ranking(&self) -> i64 {
419        self.ranking
420    }
421
422    pub fn estimated_size(&self) -> usize {
423        std::mem::size_of::<Self>()
424            + self.src.len()
425            + self.dst.len()
426            + std::mem::size_of::<String>()
427            + self.edge_type.capacity()
428    }
429}
430
431impl std::hash::Hash for EdgeHeader {
432    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
433        self.src.hash(state);
434        self.dst.hash(state);
435        self.edge_type.hash(state);
436        self.ranking.hash(state);
437    }
438}
439
440impl Ord for EdgeHeader {
441    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
442        self.src
443            .cmp(&other.src)
444            .then_with(|| self.dst.cmp(&other.dst))
445            .then_with(|| self.edge_type.cmp(&other.edge_type))
446            .then_with(|| self.ranking.cmp(&other.ranking))
447    }
448}
449
450impl PartialOrd for EdgeHeader {
451    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
452        Some(self.cmp(other))
453    }
454}
455
456/// Represents a step in a path
457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
458pub struct Step {
459    pub dst: Box<Vertex>,
460    pub edge: Box<Edge>,
461}
462
463impl std::hash::Hash for Step {
464    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
465        self.dst.hash(state);
466        self.edge.hash(state);
467    }
468}
469
470impl Ord for Step {
471    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
472        // Comparison order: dst -> edge
473        match self.dst.cmp(&other.dst) {
474            std::cmp::Ordering::Equal => self.edge.cmp(&other.edge),
475            ord => ord,
476        }
477    }
478}
479
480impl PartialOrd for Step {
481    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
482        Some(self.cmp(other))
483    }
484}
485
486impl Step {
487    pub fn new(dst: Vertex, edge_type: String, _edge_name: String, ranking: i64) -> Self {
488        let edge = Edge::new_empty(VertexId::new(), VertexId::new(), edge_type, ranking);
489        Self {
490            dst: Box::new(dst),
491            edge: Box::new(edge),
492        }
493    }
494
495    pub fn new_with_edge(dst: Vertex, edge: Edge) -> Self {
496        Self {
497            dst: Box::new(dst),
498            edge: Box::new(edge),
499        }
500    }
501
502    pub fn src_vid(&self) -> &VertexId {
503        &self.edge.src
504    }
505
506    pub fn dst_vid(&self) -> &VertexId {
507        &self.dst.vid
508    }
509
510    /// Obtaining the ranking of the edges
511    pub fn ranking(&self) -> i64 {
512        self.edge.ranking
513    }
514
515    /// Estimating the memory usage of the steps in a process
516    pub fn estimated_size(&self) -> usize {
517        let mut size = std::mem::size_of::<Self>();
518
519        // Calculate the actual sizes of dst and edge (including the heap allocation for the Box and its content).
520        size += std::mem::size_of::<Box<Vertex>>() + self.dst.estimated_size();
521        size += std::mem::size_of::<Box<Edge>>() + self.edge.estimated_size();
522
523        size
524    }
525}
526
527/// Represents a path in the graph
528#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
529pub struct Path {
530    pub src: Box<Vertex>,
531    pub steps: Vec<Step>,
532}
533
534impl Ord for Path {
535    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
536        // Use chained comparisons to improve readability.
537        self.src
538            .cmp(&other.src)
539            .then_with(|| self.steps.len().cmp(&other.steps.len()))
540            .then_with(|| self.cmp_steps(other))
541    }
542}
543
544impl Path {
545    /// Compare the steps in the paths
546    fn cmp_steps(&self, other: &Self) -> std::cmp::Ordering {
547        // Compare each step.
548        for (step1, step2) in self.steps.iter().zip(other.steps.iter()) {
549            let step_cmp = step1.cmp(step2);
550            if step_cmp != std::cmp::Ordering::Equal {
551                return step_cmp;
552            }
553        }
554        std::cmp::Ordering::Equal
555    }
556}
557
558impl PartialOrd for Path {
559    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
560        Some(self.cmp(other))
561    }
562}
563
564// Implementing a hash function manually to handle complex data types
565impl std::hash::Hash for Path {
566    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
567        self.src.hash(state);
568        for step in &self.steps {
569            step.hash(state);
570        }
571    }
572}
573
574impl Path {
575    /// Create a new path.
576    pub fn new(src: Vertex) -> Self {
577        Self {
578            src: Box::new(src),
579            steps: Vec::new(),
580        }
581    }
582
583    /// Estimate the memory usage of the path
584    pub fn estimated_size(&self) -> usize {
585        let mut size = std::mem::size_of::<Self>();
586
587        // Calculate the actual size of `src` (including the heap allocation for the `Box` and the content of the `Vertex`).
588        size += std::mem::size_of::<Box<Vertex>>() + self.src.as_ref().estimated_size();
589
590        // Calculate the memory overhead for the capacity of Vec<Step>
591        size += self.steps.capacity() * std::mem::size_of::<Step>();
592
593        for step in &self.steps {
594            size += step.estimated_size();
595        }
596
597        size
598    }
599
600    /// Add steps to the path
601    pub fn add_step(&mut self, step: Step) {
602        self.steps.push(step);
603    }
604
605    /// Obtain the edges in the path
606    pub fn edges(&self) -> Vec<&Edge> {
607        self.steps.iter().map(|step| step.edge.as_ref()).collect()
608    }
609
610    /// Get the path length (number of steps)
611    pub fn len(&self) -> usize {
612        self.steps.len()
613    }
614
615    /// Get path length (number of steps)
616    pub fn length(&self) -> usize {
617        self.steps.len()
618    }
619
620    /// Obtain the steps in the path
621    pub fn steps(&self) -> &[Step] {
622        &self.steps
623    }
624
625    /// Check whether the path is empty (contains only the source vertex).
626    pub fn is_empty(&self) -> bool {
627        self.steps.is_empty()
628    }
629
630    /// Check for duplicate edges in the path
631    pub fn has_duplicate_edges(&self) -> bool {
632        let mut seen_edges: std::collections::HashSet<(VertexId, VertexId, String)> =
633            std::collections::HashSet::new();
634
635        for step in &self.steps {
636            let edge_key = (step.edge.src, step.edge.dst, step.edge.edge_type.clone());
637
638            if !seen_edges.insert(edge_key) {
639                return true;
640            }
641        }
642
643        false
644    }
645
646    pub fn reverse(&mut self) {
647        if self.steps.is_empty() {
648            return;
649        }
650
651        // Vertex sequence from the start of this path to its end.
652        let mut vertices = Vec::with_capacity(self.steps.len() + 1);
653        vertices.push((*self.src).clone());
654        for step in &self.steps {
655            vertices.push((*step.dst).clone());
656        }
657
658        // The reversed path starts at the original end vertex. Each edge is
659        // flipped and each step's dst is set to the vertex at the flipped
660        // edge's dst, keeping edge endpoints and step dsts consistent.
661        let mut reversed_steps = Vec::with_capacity(self.steps.len());
662        for i in (0..self.steps.len()).rev() {
663            let mut edge = (*self.steps[i].edge).clone();
664            std::mem::swap(&mut edge.src, &mut edge.dst);
665            reversed_steps.push(Step {
666                dst: Box::new(vertices[i].clone()),
667                edge: Box::new(edge),
668            });
669        }
670        self.src = Box::new(vertices[vertices.len() - 1].clone());
671        self.steps = reversed_steps;
672    }
673
674    /// Append reverse path (for bidirectional BFS path splicing)
675    /// Append the reverse of another path to the current path
676    pub fn append_reverse(&mut self, other: Path) {
677        if other.steps.is_empty() {
678            return;
679        }
680
681        // Reverse steps to get other paths
682        let mut other_steps: Vec<Step> = other.steps.into_iter().rev().collect();
683
684        // Reverse the direction of each edge
685        for step in &mut other_steps {
686            std::mem::swap(&mut step.edge.src, &mut step.edge.dst);
687        }
688
689        // Append to current path
690        self.steps.extend(other_steps);
691    }
692}
693
694impl Default for Path {
695    fn default() -> Self {
696        Self {
697            src: Box::new(Vertex::default()),
698            steps: Vec::new(),
699        }
700    }
701}