Skip to main content

qualia_core_db/storage/
mmap.rs

1use crate::NQuin;
2#[cfg(not(target_arch = "wasm32"))]
3use memmap2::MmapMut;
4#[cfg(not(target_arch = "wasm32"))]
5use std::fs::OpenOptions;
6
7#[cfg(not(target_arch = "wasm32"))]
8pub struct MmapStore {
9    mmap: MmapMut,
10    capacity_quins: usize,
11    active_quins: usize,
12}
13
14#[cfg(not(target_arch = "wasm32"))]
15impl MmapStore {
16    // Opens or creates a file of exactly `capacity_quins * 48` bytes.
17    pub fn open(path: &str, capacity_quins: usize) -> Result<Self, std::io::Error> {
18        let file = OpenOptions::new()
19            .read(true)
20            .write(true)
21            .create(true)
22            .open(path)?;
23
24        let required_len = (capacity_quins * 48) as u64;
25        file.set_len(required_len)?;
26
27        let mmap = unsafe { MmapMut::map_mut(&file)? };
28
29        Ok(Self {
30            mmap,
31            capacity_quins,
32            active_quins: 0,
33        })
34    }
35
36    // Appends a Quin. Returns error if capacity exceeded.
37    pub fn append(&mut self, quin: &NQuin) -> Result<(), std::io::Error> {
38        if self.active_quins >= self.capacity_quins {
39            return Err(std::io::Error::new(
40                std::io::ErrorKind::WriteZero,
41                "MmapStore capacity exceeded",
42            ));
43        }
44
45        let offset = self.active_quins * 48;
46        let bytes = bytemuck::bytes_of(quin);
47        self.mmap[offset..offset + 48].copy_from_slice(bytes);
48
49        self.active_quins += 1;
50        Ok(())
51    }
52
53    // Returns a zero-copy slice of all active Quins.
54    pub fn as_slice(&self) -> &[NQuin] {
55        let active_bytes = self.active_quins * 48;
56        let slice = &self.mmap[..active_bytes];
57        bytemuck::cast_slice(slice)
58    }
59}
60
61#[cfg(target_arch = "wasm32")]
62pub struct MmapStore {
63    data: Vec<NQuin>,
64    capacity_quins: usize,
65}
66
67#[cfg(target_arch = "wasm32")]
68impl MmapStore {
69    pub fn open(_path: &str, capacity_quins: usize) -> Result<Self, std::io::Error> {
70        Ok(Self {
71            data: Vec::with_capacity(capacity_quins),
72            capacity_quins,
73        })
74    }
75
76    pub fn append(&mut self, quin: &NQuin) -> Result<(), std::io::Error> {
77        if self.data.len() >= self.capacity_quins {
78            return Err(std::io::Error::new(
79                std::io::ErrorKind::WriteZero,
80                "MmapStore capacity exceeded",
81            ));
82        }
83
84        self.data.push(*quin);
85        Ok(())
86    }
87
88    pub fn as_slice(&self) -> &[NQuin] {
89        &self.data
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use tempfile::NamedTempFile;
97
98    #[test]
99    fn test_mmap_store_open() {
100        let temp_file = NamedTempFile::new().unwrap();
101        let path = temp_file.path().to_str().unwrap();
102        let store = MmapStore::open(path, 10).unwrap();
103        assert_eq!(store.capacity_quins, 10);
104        assert_eq!(store.active_quins, 0);
105    }
106
107    #[test]
108    fn test_mmap_store_append_and_slice() {
109        let temp_file = NamedTempFile::new().unwrap();
110        let path = temp_file.path().to_str().unwrap();
111        let mut store = MmapStore::open(path, 10).unwrap();
112
113        let quin = NQuin {
114            subject: 1,
115            predicate: 2,
116            object: 3,
117            context: 4,
118            metadata: 5,
119            parity: 6,
120        };
121
122        store.append(&quin).unwrap();
123        assert_eq!(store.active_quins, 1);
124
125        let slice = store.as_slice();
126        assert_eq!(slice.len(), 1);
127        assert_eq!(slice[0].subject, 1);
128        assert_eq!(slice[0].predicate, 2);
129        assert_eq!(slice[0].object, 3);
130        assert_eq!(slice[0].context, 4);
131        assert_eq!(slice[0].metadata, 5);
132        assert_eq!(slice[0].parity, 6);
133    }
134}