Skip to main content

linkrs_storage/edge/mutable_csr/
iter.rs

1use std::iter::Zip;
2use std::slice::Iter as SliceIter;
3
4use linkrs_core::types::VertexId;
5
6use super::overflow::OverflowChunk;
7use super::MutableCsr;
8use crate::edge::{ColdStamps, HotNbr, Nbr};
9use linkrs_core::types::Timestamp;
10
11impl MutableCsr {
12    /// Create iterator over all edges
13    pub fn iter(&self, ts: Timestamp) -> MutableCsrIterator<'_> {
14        MutableCsrIterator::new(self, ts)
15    }
16
17    /// Create an iterator over all physically present edges, including
18    /// entries marked as deleted (delete_ts != MAX). Used when rebuilding the
19    /// CSR so tombstoned entries survive remapping.
20    pub fn iter_all(&self) -> MutableCsrIterator<'_> {
21        MutableCsrIterator::new_all(self)
22    }
23
24    /// Iterate edges of a vertex without collecting into a Vec.
25    /// Test-only row-stamp filtered iterator; production scans go through the version authority.
26    /// Filters by the delete replica only: creation stamps live in the table
27    /// authority, not in the row, so this probe can never decide full MVCC
28    /// visibility on its own.
29    pub fn iter_edges_of(&self, src_vid: u32, ts: Timestamp) -> VertexEdgesIter<'_> {
30        VertexEdgesIter::new(self, src_vid, ts)
31    }
32}
33
34/// Iterator over edges of a single vertex in MutableCsr.
35///
36/// The primary row is held as a fused slice-pair iterator, so the walk pays
37/// one bounds check per row instead of one per slot.
38pub struct VertexEdgesIter<'a> {
39    primary: Zip<SliceIter<'a, HotNbr>, SliceIter<'a, ColdStamps>>,
40    ts: Timestamp,
41    overflow_chunks: Option<&'a Vec<OverflowChunk>>,
42    overflow_chunk_idx: usize,
43    overflow_edge_idx: usize,
44}
45
46impl<'a> VertexEdgesIter<'a> {
47    pub fn new(csr: &'a MutableCsr, src_vid: u32, ts: Timestamp) -> Self {
48        let (hot, cold) = csr.primary_pair(src_vid as usize);
49        Self {
50            primary: hot.iter().zip(cold.iter()),
51            ts,
52            overflow_chunks: csr.overflow_chunks.get(src_vid),
53            overflow_chunk_idx: 0,
54            overflow_edge_idx: 0,
55        }
56    }
57}
58
59impl<'a> Iterator for VertexEdgesIter<'a> {
60    type Item = Nbr;
61
62    fn next(&mut self) -> Option<Self::Item> {
63        for (hot, cold) in self.primary.by_ref() {
64            let nbr = Nbr::from_parts(*hot, *cold);
65            if nbr.is_alive_at(self.ts) {
66                return Some(nbr);
67            }
68        }
69
70        if let Some(chunks) = self.overflow_chunks {
71            while self.overflow_chunk_idx < chunks.len() {
72                let chunk = &chunks[self.overflow_chunk_idx];
73                while self.overflow_edge_idx < chunk.len() {
74                    let nbr = chunk.slot_at(self.overflow_edge_idx).unwrap();
75                    self.overflow_edge_idx += 1;
76                    if nbr.is_alive_at(self.ts) {
77                        return Some(nbr);
78                    }
79                }
80                self.overflow_chunk_idx += 1;
81                self.overflow_edge_idx = 0;
82            }
83        }
84
85        None
86    }
87}
88
89pub struct MutableCsrIterator<'a> {
90    csr: &'a MutableCsr,
91    ts: Timestamp,
92    include_deleted: bool,
93    current_vertex: usize,
94    current_edge: usize,
95    in_overflow: bool,
96    overflow_chunks: Option<&'a Vec<OverflowChunk>>,
97    overflow_chunk_idx: usize,
98    overflow_edge_idx: usize,
99}
100
101impl<'a> MutableCsrIterator<'a> {
102    pub fn new(csr: &'a MutableCsr, ts: Timestamp) -> Self {
103        Self {
104            csr,
105            ts,
106            include_deleted: false,
107            current_vertex: 0,
108            current_edge: 0,
109            in_overflow: false,
110            overflow_chunks: None,
111            overflow_chunk_idx: 0,
112            overflow_edge_idx: 0,
113        }
114    }
115
116    /// Iterator over every stored entry, including tombstoned ones.
117    pub fn new_all(csr: &'a MutableCsr) -> Self {
118        Self {
119            csr,
120            ts: 0,
121            include_deleted: true,
122            current_vertex: 0,
123            current_edge: 0,
124            in_overflow: false,
125            overflow_chunks: None,
126            overflow_chunk_idx: 0,
127            overflow_edge_idx: 0,
128        }
129    }
130}
131
132impl<'a> Iterator for MutableCsrIterator<'a> {
133    type Item = (VertexId, Nbr);
134
135    fn next(&mut self) -> Option<Self::Item> {
136        // Copy the shared reference out so slice borrows below live for 'a
137        // and do not conflict with the cursor mutations on `self`.
138        let csr: &'a MutableCsr = self.csr;
139        while self.current_vertex < csr.vertex_capacity() {
140            if !self.in_overflow {
141                if self.current_edge == 0 {
142                    self.overflow_chunks = csr.overflow_chunks.get(self.current_vertex as u32);
143                    self.overflow_chunk_idx = 0;
144                    self.overflow_edge_idx = 0;
145                }
146                let (hot, cold) = csr.primary_pair(self.current_vertex);
147                while self.current_edge < hot.len() {
148                    let nbr = Nbr::from_parts(hot[self.current_edge], cold[self.current_edge]);
149                    self.current_edge += 1;
150                    if self.include_deleted || nbr.is_alive_at(self.ts) {
151                        return Some((
152                            VertexId::from_u32(u32::try_from(self.current_vertex).ok()?),
153                            nbr,
154                        ));
155                    }
156                }
157                self.in_overflow = true;
158            }
159
160            if let Some(chunks) = self.overflow_chunks {
161                while self.overflow_chunk_idx < chunks.len() {
162                    let chunk = &chunks[self.overflow_chunk_idx];
163                    while self.overflow_edge_idx < chunk.len() {
164                        let nbr = chunk.slot_at(self.overflow_edge_idx).unwrap();
165                        self.overflow_edge_idx += 1;
166                        if self.include_deleted || nbr.is_alive_at(self.ts) {
167                            return Some((
168                                VertexId::from_u32(u32::try_from(self.current_vertex).ok()?),
169                                nbr,
170                            ));
171                        }
172                    }
173                    self.overflow_chunk_idx += 1;
174                    self.overflow_edge_idx = 0;
175                }
176            }
177
178            self.current_vertex += 1;
179            self.current_edge = 0;
180            self.in_overflow = false;
181            self.overflow_chunk_idx = 0;
182            self.overflow_edge_idx = 0;
183        }
184        None
185    }
186}