uqa_storage/diskann_index/format/
origins.rs1use sha2::{Digest, Sha256};
8use uqa_core::{memory::BudgetedVec, DocId};
9
10use super::{invalid, record, DiskANNCanonicalOrigin, DiskANNGeneration, CANONICAL_ORIGIN_BYTES};
11use crate::{read_control::StorageReadControl, StorageBackendResult};
12
13#[cfg(test)]
14mod tests;
15
16const MAGIC: [u8; 8] = *b"UQADNOR\0";
17const METADATA_BYTES: usize = 32;
18pub const ORIGIN_ENTRY_BYTES: usize = 8 + CANONICAL_ORIGIN_BYTES;
19pub const ORIGIN_BATCH_DOCUMENTS: usize = 64;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct DiskANNOriginSummary {
24 documents: u64,
25 digest: [u8; 32],
26}
27
28impl DiskANNOriginSummary {
29 pub const ENCODED_BYTES: usize = 40;
30
31 pub(crate) fn new(documents: u64, digest: [u8; 32]) -> StorageBackendResult<Self> {
32 if documents == 0 && digest != <[u8; 32]>::from(Sha256::digest([])) {
33 return Err(invalid("empty origin stream requires the empty digest"));
34 }
35 Ok(Self { documents, digest })
36 }
37
38 pub fn documents(self) -> u64 {
39 self.documents
40 }
41 pub fn digest(self) -> [u8; 32] {
42 self.digest
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct DiskANNOriginEntry {
49 document: DocId,
50 origin: DiskANNCanonicalOrigin,
51}
52
53impl DiskANNOriginEntry {
54 pub fn new(document: DocId, origin: DiskANNCanonicalOrigin) -> Self {
55 Self { document, origin }
56 }
57 pub fn document(self) -> DocId {
58 self.document
59 }
60 pub fn origin(self) -> DiskANNCanonicalOrigin {
61 self.origin
62 }
63
64 pub(crate) fn encode(self) -> [u8; ORIGIN_ENTRY_BYTES] {
65 let mut bytes = [0; ORIGIN_ENTRY_BYTES];
66 bytes[..8].copy_from_slice(&self.document.to_le_bytes());
67 bytes[8..].copy_from_slice(&self.origin.encode());
68 bytes
69 }
70
71 pub(crate) fn decode(bytes: &[u8], dimensions: u32) -> StorageBackendResult<Self> {
72 if bytes.len() != ORIGIN_ENTRY_BYTES {
73 return Err(invalid("origin entry length differs"));
74 }
75 Ok(Self::new(
76 record::u64_at(bytes, 0)?,
77 DiskANNCanonicalOrigin::decode(&bytes[8..], dimensions)?,
78 ))
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
84pub struct DiskANNOriginLayout {
85 generation: DiskANNGeneration,
86 dimensions: u32,
87 documents: u64,
88}
89
90pub struct DiskANNOriginBatch<'a> {
91 dimensions: u32,
92 entries: &'a [u8],
93}
94
95impl DiskANNOriginBatch<'_> {
96 pub fn len(&self) -> usize {
97 self.entries.len() / ORIGIN_ENTRY_BYTES
98 }
99 pub fn is_empty(&self) -> bool {
100 self.entries.is_empty()
101 }
102 pub fn bytes(&self) -> &[u8] {
103 self.entries
104 }
105 pub fn entry(&self, index: usize) -> Option<DiskANNOriginEntry> {
106 let start = index.checked_mul(ORIGIN_ENTRY_BYTES)?;
107 let end = start.checked_add(ORIGIN_ENTRY_BYTES)?;
108 Some(
109 DiskANNOriginEntry::decode(self.entries.get(start..end)?, self.dimensions)
110 .expect("validated origin entry"),
111 )
112 }
113}
114
115impl DiskANNOriginLayout {
116 pub const MAX_ENCODED_BYTES: usize =
117 record::HEADER_BYTES + METADATA_BYTES + ORIGIN_BATCH_DOCUMENTS * ORIGIN_ENTRY_BYTES;
118
119 pub fn new(
120 generation: DiskANNGeneration,
121 dimensions: u32,
122 documents: u64,
123 ) -> StorageBackendResult<Self> {
124 if dimensions == 0 {
125 return Err(invalid("origin dimensions must be positive"));
126 }
127 Ok(Self {
128 generation,
129 dimensions,
130 documents,
131 })
132 }
133
134 pub fn encode(
135 self,
136 first: u64,
137 entries: &[DiskANNOriginEntry],
138 control: &StorageReadControl,
139 ) -> StorageBackendResult<BudgetedVec<u8>> {
140 self.check_extent(first, entries.len())?;
141 let mut bytes = record::begin(
142 MAGIC,
143 self.generation,
144 self.encoded_bytes(first)? - record::HEADER_BYTES,
145 control,
146 )?;
147 bytes.extend_from_slice(&self.dimensions.to_le_bytes())?;
148 bytes.extend_from_slice(&(ORIGIN_BATCH_DOCUMENTS as u32).to_le_bytes())?;
149 for value in [self.documents, first, entries.len() as u64] {
150 bytes.extend_from_slice(&value.to_le_bytes())?;
151 }
152 let mut previous = None;
153 for &entry in entries {
154 control.check()?;
155 let encoded = entry.encode();
156 DiskANNOriginEntry::decode(&encoded, self.dimensions)?;
157 ordered(&mut previous, entry)?;
158 bytes.extend_from_slice(&encoded)?;
159 }
160 record::finish(bytes, control)
161 }
162
163 pub fn decode<'a>(
164 self,
165 first: u64,
166 bytes: &'a [u8],
167 control: &StorageReadControl,
168 ) -> StorageBackendResult<DiskANNOriginBatch<'a>> {
169 let body = record::open(MAGIC, self.generation, bytes, control)?;
170 if body.len() < METADATA_BYTES
171 || !(body.len() - METADATA_BYTES).is_multiple_of(ORIGIN_ENTRY_BYTES)
172 {
173 return Err(invalid("origin batch length differs"));
174 }
175 let entries = &body[METADATA_BYTES..];
176 let count = entries.len() / ORIGIN_ENTRY_BYTES;
177 self.check_extent(first, count)?;
178 if record::u32_at(body, 0)? != self.dimensions
179 || record::u32_at(body, 4)? != ORIGIN_BATCH_DOCUMENTS as u32
180 || record::u64_at(body, 8)? != self.documents
181 || record::u64_at(body, 16)? != first
182 || record::u64_at(body, 24)? != count as u64
183 {
184 return Err(invalid("origin batch shape or address differs"));
185 }
186 let mut previous = None;
187 for bytes in entries.as_chunks::<ORIGIN_ENTRY_BYTES>().0 {
188 control.check()?;
189 ordered(
190 &mut previous,
191 DiskANNOriginEntry::decode(bytes, self.dimensions)?,
192 )?;
193 }
194 control.check()?;
195 Ok(DiskANNOriginBatch {
196 dimensions: self.dimensions,
197 entries,
198 })
199 }
200
201 fn check_extent(self, first: u64, count: usize) -> StorageBackendResult<()> {
202 if first >= self.documents
203 || !first.is_multiple_of(ORIGIN_BATCH_DOCUMENTS as u64)
204 || count as u64 != (self.documents - first).min(ORIGIN_BATCH_DOCUMENTS as u64)
205 {
206 return Err(invalid(
207 "origin batch does not cover its exact fixed extent",
208 ));
209 }
210 Ok(())
211 }
212
213 pub fn encoded_bytes(self, first: u64) -> StorageBackendResult<usize> {
214 let count = self
215 .documents
216 .saturating_sub(first)
217 .min(ORIGIN_BATCH_DOCUMENTS as u64) as usize;
218 self.check_extent(first, count)?;
219 Ok(record::HEADER_BYTES + METADATA_BYTES + count * ORIGIN_ENTRY_BYTES)
220 }
221}
222
223fn ordered(previous: &mut Option<DocId>, entry: DiskANNOriginEntry) -> StorageBackendResult<()> {
224 if previous.is_some_and(|last| last >= entry.document) {
225 return Err(invalid("origin documents must be unique and ordered"));
226 }
227 *previous = Some(entry.document);
228 Ok(())
229}