Skip to main content

uni_store/runtime/
vid_remapper.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4//! Bidirectional mapping between sparse VIDs and dense array indices.
5//!
6//! During query execution, we load subgraphs into memory. VIDs are sparse
7//! (auto-increment with gaps from deletions), but we want O(1) array access.
8//! VidRemapper provides this mapping.
9
10use std::collections::HashMap;
11use uni_common::core::id::{DenseIdx, Vid};
12
13/// Bidirectional mapping between sparse Vid and dense array indices.
14///
15/// Used during subgraph loading and query execution to enable O(1) array
16/// access while preserving the ability to convert back to VIDs for results.
17#[derive(Debug, Clone, Default)]
18pub struct VidRemapper {
19    /// Sparse VID → dense index
20    vid_to_dense: HashMap<Vid, DenseIdx>,
21    /// Dense index → sparse VID (O(1) reverse lookup)
22    dense_to_vid: Vec<Vid>,
23}
24
25impl VidRemapper {
26    /// Creates a new empty remapper.
27    pub fn new() -> Self {
28        Self {
29            vid_to_dense: HashMap::new(),
30            dense_to_vid: Vec::new(),
31        }
32    }
33
34    /// Creates a remapper with pre-allocated capacity.
35    pub fn with_capacity(capacity: usize) -> Self {
36        Self {
37            vid_to_dense: HashMap::with_capacity(capacity),
38            dense_to_vid: Vec::with_capacity(capacity),
39        }
40    }
41
42    /// Assigns a dense index to a VID (during subgraph load).
43    ///
44    /// If the VID is already mapped, returns the existing index.
45    /// Otherwise, assigns the next available index.
46    pub fn insert(&mut self, vid: Vid) -> DenseIdx {
47        if let Some(&idx) = self.vid_to_dense.get(&vid) {
48            return idx;
49        }
50        let idx = DenseIdx::new(self.dense_to_vid.len() as u32);
51        self.dense_to_vid.push(vid);
52        self.vid_to_dense.insert(vid, idx);
53        idx
54    }
55
56    /// Inserts multiple VIDs at once, returning their dense indices.
57    pub fn insert_many(&mut self, vids: &[Vid]) -> Vec<DenseIdx> {
58        vids.iter().map(|&vid| self.insert(vid)).collect()
59    }
60
61    /// VID → DenseIdx (for array indexing)
62    ///
63    /// Returns None if the VID is not mapped.
64    pub fn to_dense(&self, vid: Vid) -> Option<DenseIdx> {
65        self.vid_to_dense.get(&vid).copied()
66    }
67
68    /// VID → DenseIdx, panicking if not found.
69    ///
70    /// Use when you're certain the VID was already inserted.
71    pub fn to_dense_unchecked(&self, vid: Vid) -> DenseIdx {
72        self.vid_to_dense[&vid]
73    }
74
75    /// DenseIdx → VID (for returning results)
76    ///
77    /// Panics if the index is out of bounds.
78    pub fn to_vid(&self, idx: DenseIdx) -> Vid {
79        self.dense_to_vid[idx.as_usize()]
80    }
81
82    /// DenseIdx → VID, returning None if out of bounds.
83    pub fn to_vid_opt(&self, idx: DenseIdx) -> Option<Vid> {
84        self.dense_to_vid.get(idx.as_usize()).copied()
85    }
86
87    /// Returns the number of mapped VIDs.
88    pub fn len(&self) -> usize {
89        self.dense_to_vid.len()
90    }
91
92    /// Returns true if no VIDs are mapped.
93    pub fn is_empty(&self) -> bool {
94        self.dense_to_vid.is_empty()
95    }
96
97    /// Checks if a VID is already mapped.
98    pub fn contains(&self, vid: Vid) -> bool {
99        self.vid_to_dense.contains_key(&vid)
100    }
101
102    /// Returns an iterator over all (DenseIdx, Vid) pairs.
103    pub fn iter(&self) -> impl Iterator<Item = (DenseIdx, Vid)> + '_ {
104        self.dense_to_vid
105            .iter()
106            .enumerate()
107            .map(|(i, &vid)| (DenseIdx::new(i as u32), vid))
108    }
109
110    /// Returns an iterator over all VIDs in dense order.
111    pub fn vids(&self) -> impl Iterator<Item = Vid> + '_ {
112        self.dense_to_vid.iter().copied()
113    }
114
115    /// Returns a slice of all VIDs in dense order.
116    pub fn vids_slice(&self) -> &[Vid] {
117        &self.dense_to_vid
118    }
119
120    /// Clears all mappings.
121    pub fn clear(&mut self) {
122        self.vid_to_dense.clear();
123        self.dense_to_vid.clear();
124    }
125
126    /// Memory usage in bytes (approximate).
127    pub fn memory_usage(&self) -> usize {
128        // HashMap overhead + entries + Vec capacity
129        self.vid_to_dense.capacity()
130            * (std::mem::size_of::<Vid>() + std::mem::size_of::<DenseIdx>())
131            + self.dense_to_vid.capacity() * std::mem::size_of::<Vid>()
132    }
133}
134
135/// Bidirectional mapping between sparse EIDs and dense array indices.
136///
137/// Similar to VidRemapper but for edge IDs.
138#[derive(Debug, Clone, Default)]
139pub struct EidRemapper {
140    eid_to_dense: HashMap<uni_common::core::id::Eid, DenseIdx>,
141    dense_to_eid: Vec<uni_common::core::id::Eid>,
142}
143
144impl EidRemapper {
145    pub fn new() -> Self {
146        Self {
147            eid_to_dense: HashMap::new(),
148            dense_to_eid: Vec::new(),
149        }
150    }
151
152    pub fn with_capacity(capacity: usize) -> Self {
153        Self {
154            eid_to_dense: HashMap::with_capacity(capacity),
155            dense_to_eid: Vec::with_capacity(capacity),
156        }
157    }
158
159    pub fn insert(&mut self, eid: uni_common::core::id::Eid) -> DenseIdx {
160        if let Some(&idx) = self.eid_to_dense.get(&eid) {
161            return idx;
162        }
163        let idx = DenseIdx::new(self.dense_to_eid.len() as u32);
164        self.dense_to_eid.push(eid);
165        self.eid_to_dense.insert(eid, idx);
166        idx
167    }
168
169    pub fn to_dense(&self, eid: uni_common::core::id::Eid) -> Option<DenseIdx> {
170        self.eid_to_dense.get(&eid).copied()
171    }
172
173    pub fn to_eid(&self, idx: DenseIdx) -> uni_common::core::id::Eid {
174        self.dense_to_eid[idx.as_usize()]
175    }
176
177    pub fn len(&self) -> usize {
178        self.dense_to_eid.len()
179    }
180
181    pub fn is_empty(&self) -> bool {
182        self.dense_to_eid.is_empty()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_vid_remapper_basic() {
192        let mut remapper = VidRemapper::new();
193
194        let vid1 = Vid::new(100);
195        let vid2 = Vid::new(500);
196        let vid3 = Vid::new(200);
197
198        let idx1 = remapper.insert(vid1);
199        let idx2 = remapper.insert(vid2);
200        let idx3 = remapper.insert(vid3);
201
202        assert_eq!(idx1.as_u32(), 0);
203        assert_eq!(idx2.as_u32(), 1);
204        assert_eq!(idx3.as_u32(), 2);
205
206        assert_eq!(remapper.to_vid(idx1), vid1);
207        assert_eq!(remapper.to_vid(idx2), vid2);
208        assert_eq!(remapper.to_vid(idx3), vid3);
209
210        assert_eq!(remapper.to_dense(vid1), Some(idx1));
211        assert_eq!(remapper.to_dense(vid2), Some(idx2));
212        assert_eq!(remapper.to_dense(Vid::new(999)), None);
213    }
214
215    #[test]
216    fn test_vid_remapper_duplicate_insert() {
217        let mut remapper = VidRemapper::new();
218        let vid = Vid::new(42);
219
220        let idx1 = remapper.insert(vid);
221        let idx2 = remapper.insert(vid);
222
223        assert_eq!(idx1, idx2);
224        assert_eq!(remapper.len(), 1);
225    }
226
227    #[test]
228    fn test_vid_remapper_insert_many() {
229        let mut remapper = VidRemapper::new();
230        let vids = vec![Vid::new(10), Vid::new(20), Vid::new(30)];
231
232        let indices = remapper.insert_many(&vids);
233
234        assert_eq!(indices.len(), 3);
235        assert_eq!(remapper.len(), 3);
236        for (idx, vid) in indices.iter().zip(vids.iter()) {
237            assert_eq!(remapper.to_vid(*idx), *vid);
238        }
239    }
240
241    #[test]
242    fn test_vid_remapper_iter() {
243        let mut remapper = VidRemapper::new();
244        remapper.insert(Vid::new(100));
245        remapper.insert(Vid::new(200));
246        remapper.insert(Vid::new(300));
247
248        let pairs: Vec<_> = remapper.iter().collect();
249        assert_eq!(pairs.len(), 3);
250        assert_eq!(pairs[0], (DenseIdx::new(0), Vid::new(100)));
251        assert_eq!(pairs[1], (DenseIdx::new(1), Vid::new(200)));
252        assert_eq!(pairs[2], (DenseIdx::new(2), Vid::new(300)));
253    }
254}