qualia_core_db/q42/
q42_reader.rs1use std::fs::File;
7use std::io::Read;
8use std::path::Path;
9
10use crate::NQuin;
11
12pub fn read_c_q42_quins(path: &Path) -> std::io::Result<Vec<NQuin>> {
18 let mut file = File::open(path)?;
19 let file_len = file.metadata()?.len();
20 let mut offset = 0u64;
21 let mut quins = Vec::new();
22 let quin_size = std::mem::size_of::<NQuin>();
23
24 while offset < file_len {
25 let mut header = [0u8; 16];
26 if file.read_exact(&mut header).is_err() {
27 break;
28 }
29 offset += 16;
30
31 let compressed_len = u32::from_le_bytes(header[8..12].try_into().unwrap()) as usize;
32 let mut compressed = vec![0u8; compressed_len];
33 file.read_exact(&mut compressed)?;
34 offset += compressed_len as u64;
35
36 let uncompressed = lz4_flex::decompress_size_prepended(&compressed)
37 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
38
39 for chunk in uncompressed.chunks_exact(quin_size) {
40 let quin: NQuin = unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const NQuin) };
41 quins.push(quin);
42 }
43 }
44
45 Ok(quins)
46}
47
48pub fn read_q42_quins(path: &Path) -> std::io::Result<Vec<NQuin>> {
55 if let Ok(volume) = crate::q42_volume::Q42Volume::open(path) {
56 if volume.volume_manifest()?.is_some() {
57 let set = crate::q42_volume::Q42VolumeSet::open_root(path)?;
58 let mut quins = Vec::new();
59 for segment in set.segments() {
60 quins.extend(segment.read_all_quins()?);
61 }
62 return Ok(quins);
63 }
64 return volume.read_all_quins();
65 }
66 read_c_q42_quins(path)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::{q_hash, NQuin};
73 use std::io::Write;
74
75 fn write_test_c_q42(path: &Path, quins: &[NQuin]) {
76 let mut file = File::create(path).unwrap();
77 let bytes: Vec<u8> = quins
78 .iter()
79 .flat_map(|q| bytemuck::bytes_of(q).iter().copied())
80 .collect();
81 let compressed = lz4_flex::compress_prepend_size(&bytes);
82 file.write_all(&0u64.to_le_bytes()).unwrap();
83 file.write_all(&(compressed.len() as u32).to_le_bytes())
84 .unwrap();
85 file.write_all(&(bytes.len() as u32).to_le_bytes()).unwrap();
86 file.write_all(&compressed).unwrap();
87 }
88
89 #[test]
90 fn roundtrip_read_c_q42() {
91 let dir = std::env::temp_dir().join(format!("q42-read-{}", std::process::id()));
92 std::fs::create_dir_all(&dir).unwrap();
93 let path = dir.join("test.c.q42");
94 let s = q_hash("ex:subject");
95 let p = q_hash("ex:predicate");
96 let o = q_hash("ex:object");
97 let quin = NQuin {
98 subject: s,
99 predicate: p,
100 object: o,
101 context: 0,
102 metadata: 0,
103 parity: s ^ p ^ o,
104 };
105 write_test_c_q42(&path, &[quin]);
106 let loaded = read_c_q42_quins(&path).unwrap();
107 assert_eq!(loaded.len(), 1);
108 assert_eq!(loaded[0].subject, s);
109 let _ = std::fs::remove_dir_all(dir);
110 }
111
112 #[test]
113 fn canonical_volume_reader_dispatches_to_unified_v3() {
114 let dir = tempfile::TempDir::new().unwrap();
115 let path = dir.path().join("canonical.q42");
116 let quin = NQuin {
117 subject: 1,
118 predicate: 2,
119 object: 3,
120 context: 0,
121 metadata: 0,
122 parity: 0,
123 };
124 crate::q42_volume::write_unified_volume(
125 &path,
126 &std::collections::HashMap::new(),
127 &[(3, 3)],
128 &[vec![quin]],
129 )
130 .unwrap();
131 assert_eq!(read_q42_quins(&path).unwrap(), vec![quin]);
132 }
133}