1use super::{ChunkEnt, QTileEnt, TileEnt, TokenEnt};
7use crate::file::{ByteRange, FileError};
8use crate::header::Header;
9use crate::reader::RangeReader;
10
11#[derive(Debug, Clone, Default)]
13pub struct DictPart {
14 pub term_count: u32,
16 pub restart_interval: u32,
18 pub chunks: Vec<(ChunkEnt, u64)>,
20 pub max_raw: u64,
22}
23
24#[derive(Debug, Clone, Default)]
26pub struct PermPart {
27 pub si: usize,
29 pub tiles: Vec<(TileEnt, u64)>,
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct QuadPart {
36 pub graph_count: u64,
38 pub quad_count: u64,
40 pub block_graphs: u64,
42 pub p0: Vec<u8>,
44 pub gdir: ByteRange,
46 pub orders: Vec<(usize, Vec<QTileEnt>)>,
48}
49
50#[derive(Debug, Clone, Default)]
52pub struct LabelPart {
53 pub head: Vec<u8>,
55 pub data_len: u64,
57 pub body: ByteRange,
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct TextPart {
64 pub scope: Vec<u8>,
66 pub tokens: Vec<TokenEnt>,
68 pub postings: ByteRange,
70 pub partial: bool,
72 pub named: bool,
74}
75
76#[derive(Debug, Clone, Default)]
78pub struct Parts {
79 pub quad_count: u64,
81 pub term_count: u64,
83 pub perm_mask: u8,
85 pub dict_codec: u8,
87 pub block_codec: u8,
89 pub pyramid_levels: u16,
91 pub named: bool,
93 pub quoted: bool,
95 pub card: Option<Vec<u8>>,
97 pub build: Option<Vec<u8>>,
99 pub schema: Option<Vec<u8>>,
101 pub dict: [DictPart; 4],
103 pub index: Vec<PermPart>,
105 pub quad: Option<QuadPart>,
107 pub labels: Option<LabelPart>,
109 pub text: Option<TextPart>,
111 pub pyramid: Option<ByteRange>,
113}
114
115fn read(r: &dyn RangeReader, range: ByteRange) -> Result<Vec<u8>, FileError> {
116 if range.len == 0 {
117 return Ok(Vec::new());
118 }
119 Ok(r.read_at(range.offset, range.len)?)
120}
121
122fn first_last(body: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
124 let mut pos = 0usize;
125 let mut buf: Vec<u8> = Vec::new();
126 let mut first = None;
127 while pos < body.len() {
128 let (shared, n1) = crate::varint::read_uvarint(body.get(pos..)?)?;
129 let p = pos + n1;
130 let (suf, n2) = crate::varint::read_uvarint(body.get(p..)?)?;
131 let start = p + n2;
132 let end = start.checked_add(suf as usize)?;
133 let s = body.get(start..end)?;
134 if shared as usize > buf.len() {
135 return None;
136 }
137 buf.truncate(shared as usize);
138 buf.extend_from_slice(s);
139 if first.is_none() {
140 first = Some(buf.clone());
141 }
142 pos = end;
143 }
144 Some((first?, buf))
145}
146
147fn rekey(r: &dyn RangeReader, codec: u8, chunks: &mut [(ChunkEnt, u64)]) -> Result<u64, FileError> {
150 const BATCH: u64 = 8 << 20;
151 let mut max_raw = 0u64;
152 let mut prev_last: Option<Vec<u8>> = None;
153 let mut i = 0usize;
154 while i < chunks.len() {
155 let start = chunks[i].1;
157 let mut j = i;
158 let mut end = start;
159 while j < chunks.len() && chunks[j].1 == end && (end - start < BATCH || j == i) {
160 end += chunks[j].0.clen;
161 j += 1;
162 }
163 let span = r.read_at(start, end - start)?;
164 for c in chunks.iter_mut().take(j).skip(i) {
165 let lo = (c.1 - start) as usize;
166 let stored = span
167 .get(lo..lo + c.0.clen as usize)
168 .ok_or(FileError::Container("dictionary chunk overruns"))?;
169 let body = crate::file::decompress(codec, stored)?;
170 max_raw = max_raw.max(body.len() as u64);
171 let (first, last) =
172 first_last(&body).ok_or(FileError::Container("malformed dictionary chunk"))?;
173 c.0.key = match &prev_last {
174 None => Vec::new(),
175 Some(pl) => crate::dict::shortest_separator(pl, &first),
176 };
177 prev_last = Some(last);
178 }
179 i = j;
180 }
181 Ok(max_raw)
182}
183
184pub(crate) fn label_part<R: RangeReader + ?Sized>(
188 r: &R,
189 offset: u64,
190 length: u64,
191 codec: u8,
192) -> Result<LabelPart, FileError> {
193 let head_len = crate::label_index::HEAD_READ.min(length);
194 let head = r.read_at(offset, head_len)?;
195 let pre = crate::label_index::LabelIndexPreamble::parse(&head)?;
196 let root_end = 32u64 + pre.root_len as u64;
197 let mut lhead = if root_end as usize <= head.len() {
198 head[..root_end as usize].to_vec()
199 } else {
200 r.read_at(offset, root_end)?
201 };
202 lhead[..16].fill(0);
203 let rr = |o: u64, l: u64| r.read_at(offset + o, l).ok();
204 let li = crate::label_index::LabelIndex::from_head(
205 &lhead,
206 length,
207 codec,
208 Box::new(move |_o: u64, _l: u64| None),
209 )?;
210 let data_len = li
211 .v05_data_len(&rr)
212 .ok_or(FileError::Container("label index: unreadable directory"))?;
213 Ok(LabelPart {
214 head: lhead,
215 data_len,
216 body: ByteRange {
217 offset: offset + root_end,
218 len: length - root_end,
219 },
220 })
221}
222
223pub fn from_v05(r: &dyn RangeReader) -> Result<Parts, FileError> {
229 let head = r.read_at(0, crate::header::HEADER_LEN as u64)?;
230 let h = Header::from_bytes(&head)?;
231 if h.version != 0x05 {
232 return Err(FileError::Container("not a 0x05 file"));
233 }
234 if h.named_graphs_len > 0 {
235 return Err(FileError::Container(
236 "per-graph named graphs (kind 5): convert re-encodes them as a quad index",
237 ));
238 }
239 let mut p = Parts {
240 quad_count: h.quad_count,
241 term_count: h.term_count,
242 perm_mask: h.perms.bits(),
243 dict_codec: h.dict_codec,
244 block_codec: h.block_codec,
245 pyramid_levels: h.pyramid_levels,
246 named: h.has_quads(),
247 quoted: h.flags & crate::header::FLAG_HAS_QUOTED_TRIPLES != 0,
248 ..Default::default()
249 };
250 if h.metadata_len > 0 {
251 p.card = Some(read(
252 r,
253 ByteRange {
254 offset: h.metadata_offset,
255 len: h.metadata_len,
256 },
257 )?);
258 }
259 if h.build_info_len > 0 {
260 p.build = Some(read(
261 r,
262 ByteRange {
263 offset: h.build_info_offset,
264 len: h.build_info_len,
265 },
266 )?);
267 }
268 if let Some(s) = h.schema_section() {
269 let b = read(
270 r,
271 ByteRange {
272 offset: s.offset,
273 len: s.length,
274 },
275 )?;
276 if b.len() > 16 {
277 p.schema = Some(b[16..].to_vec());
278 }
279 }
280 for si in 0..4 {
282 let sec = crate::file::locate_container_section_ranged(
283 r,
284 h.dictionary_offset,
285 h.dictionary_len,
286 si,
287 4,
288 )?;
289 let (meta, entries) = crate::file::read_dict_dir_ranged(r, sec)?;
290 let mut chunks: Vec<(ChunkEnt, u64)> = entries
291 .into_iter()
292 .map(|e| {
293 (
294 ChunkEnt {
295 first_run: e.first_run,
296 key: e.key,
297 clen: e.end - e.start,
298 },
299 sec.offset + e.start,
300 )
301 })
302 .collect();
303 let max_raw = rekey(r, h.dict_codec, &mut chunks)?;
304 p.dict[si] = DictPart {
305 term_count: meta.term_count,
306 restart_interval: meta.restart_interval,
307 chunks,
308 max_raw,
309 };
310 }
311 let sections = h.index_container_sections();
313 for perm in h.perms.iter() {
314 let pos = h.perms.position(perm).unwrap_or(0);
315 let sec = crate::file::locate_container_section_ranged(
316 r,
317 h.root_dir_offset,
318 h.root_dir_len,
319 pos,
320 sections as u64,
321 )?;
322 let dir = crate::file::read_tile_directory_ranged(r, sec)?;
323 let syn = if h.has_tile_synopsis() {
324 crate::file::read_tile_synopsis_ranged(r, sec, &dir)
325 } else {
326 vec![None; dir.len()]
327 };
328 let mut tiles = Vec::with_capacity(dir.len());
329 for (e, s) in dir.iter().zip(syn) {
330 let range = ByteRange {
331 offset: sec.offset + e.start,
332 len: e.end - e.start,
333 };
334 let syn = match s {
335 Some(s) => s,
336 None => {
337 let stored = r.read_at(range.offset, range.len)?;
340 let body = crate::file::decompress(h.block_codec, &stored)?;
341 let blk = crate::triples::TripleBlock::parse(&body)
342 .map_err(|_| FileError::Container("malformed tile"))?;
343 let z = blk.zone();
344 (z.min_b, z.max_b, z.min_c, z.max_c)
345 }
346 };
347 tiles.push((
348 TileEnt {
349 min_a: e.min_a,
350 max_a: e.max_a,
351 clen: range.len,
352 syn,
353 },
354 range.offset,
355 ));
356 }
357 p.index.push(PermPart {
358 si: perm.section_index(),
359 tiles,
360 });
361 }
362 if let Some(q) = h.quad_index_section() {
364 p.quad = Some(crate::quad::v05_parts(
365 r,
366 ByteRange {
367 offset: q.offset,
368 len: q.length,
369 },
370 h.perms,
371 )?);
372 }
373 if let Some(s) = h.label_index_section() {
375 p.labels = Some(label_part(r, s.offset, s.length, h.block_codec)?);
376 }
377 let (toff, tlen) = h.text_index_payload();
379 if tlen > 0 {
380 let probe = r.read_at(toff, 10.min(tlen))?;
381 let (ttl, n) = crate::varint::read_uvarint(&probe)
382 .ok_or(FileError::Container("truncated text-index len"))?;
383 let prefix_len = (n as u64 + ttl).min(tlen);
384 let prefix = r.read_at(toff, prefix_len)?;
385 let (tokens, scope, scope_raw, pstart) =
386 crate::text_index::parse_token_table_raw(&prefix, h.block_codec)?;
387 p.text = Some(TextPart {
388 scope: scope_raw,
389 tokens: tokens
390 .into_iter()
391 .map(|(t, off, len)| TokenEnt {
392 token: t.into_bytes(),
393 off,
394 len,
395 })
396 .collect(),
397 postings: ByteRange {
398 offset: toff + pstart as u64,
399 len: tlen - pstart as u64,
400 },
401 partial: scope.restricts_predicates(),
402 named: scope.covers_named_graphs(),
403 });
404 }
405 if h.pyramid_meta_len > 0 {
406 p.pyramid = Some(ByteRange {
407 offset: h.pyramid_meta_offset,
408 len: h.pyramid_meta_len,
409 });
410 }
411 Ok(p)
412}