Skip to main content

qualia_core_db/q42/
q42_reader.rs

1//! Read `.q42` graphs for cold-path callers (chat retrieval, daemon vault load).
2//!
3//! Canonical files are unified v3 volumes. Legacy 16-byte framed LZ4
4//! (`.c.q42` transport) remains a fallback only.
5
6use std::fs::File;
7use std::io::Read;
8use std::path::Path;
9
10use crate::NQuin;
11
12/// Decompress all quin slots from a legacy framed compressed transport file.
13///
14/// Prefer this function only for `.c.q42`-style payloads or other explicitly
15/// legacy framed artifacts. Canonical raw `.q42` readers should operate on
16/// 40,960-byte `QualiaSuperBlock` pages directly.
17pub 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
48/// Legacy compatibility wrapper.
49///
50/// Prefer canonical v3 Q42 containers (including front-embedded logical
51/// volume roots), then fall back to the explicitly legacy framed transport.
52/// This keeps existing daemon/SPARQL cold-load callers working while they
53/// migrate to block-oriented query sources.
54pub 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}