1#![forbid(unsafe_code)]
8
9use std::cmp::Ordering;
10use std::collections::HashMap;
11use std::fs::{File, OpenOptions};
12use std::io::{Read, Seek, SeekFrom, Write};
13use std::mem::size_of;
14use std::path::Path;
15use std::sync::Mutex;
16use std::sync::{Arc, OnceLock};
17
18use rudb_common::bounds::Bound;
19use rudb_common::{Error, Field, LogicalType, Result, Value};
20use rudb_storage::{Probe, Range, Zone};
21use rudb_vector::string::StringColumn;
22use rudb_vector::validity::Validity;
23use rudb_vector::{Buffer, Chunk, Data, TextSource, Vector};
24
25const MAGIC_V7: &[u8; 8] = b"RUDBNV7\0";
26const MAGIC: &[u8; 8] = b"RUDBNV8\0";
27const DIRECTORY_V7: &[u8; 8] = b"RUDBDIR7";
28const DIRECTORY: &[u8; 8] = b"RUDBDIR8";
29const HEADER: u64 = 80;
30const SLOT_BYTES: usize = 28;
31const MAX_PAGE: usize = 256 * 1024 * 1024;
32const MAX_DIRECTORY: usize = 128 * 1024 * 1024;
33const FREQUENCIES_V1: &[u8; 8] = b"RUDBFQ1\0";
34const FREQUENCIES: &[u8; 8] = b"RUDBFQ2\0";
35const FREQUENCY_CANDIDATES: usize = 32_768;
36const FREQUENCY_ENTRIES: usize = 512;
37const FREQUENCY_BUILD_RANK: usize = 10;
38const FREQUENCY_ORDINALS: usize = 65_536;
39const MAX_FREQUENCY_WORKERS: usize = 16;
40
41fn io(error: std::io::Error) -> Error {
42 Error::io(error.to_string())
43}
44
45fn invalid(message: &str) -> Error {
46 Error::invalid_input(format!("invalid rudb native file: {message}"))
47}
48
49fn checksum(bytes: &[u8]) -> u64 {
50 const P1: u64 = 11_400_714_785_074_694_791;
51 const P2: u64 = 14_029_467_366_897_019_727;
52 const P3: u64 = 1_609_587_929_392_839_161;
53 const P4: u64 = 9_650_029_242_287_828_579;
54 const P5: u64 = 2_870_177_450_012_600_261;
55 let round = |state: u64, word: u64| {
56 state.wrapping_add(word.wrapping_mul(P2)).rotate_left(31).wrapping_mul(P1)
57 };
58 let merge = |state: u64, lane: u64| (state ^ round(0, lane)).wrapping_mul(P1).wrapping_add(P4);
59 let word =
60 |at: usize| u64::from_le_bytes(bytes[at..at + 8].try_into().expect("eight checksum bytes"));
61
62 let mut at = 0;
63 let mut hash = if bytes.len() >= 32 {
64 let mut one = P1.wrapping_add(P2);
65 let mut two = P2;
66 let mut three = 0;
67 let mut four = 0_u64.wrapping_sub(P1);
68 while at + 32 <= bytes.len() {
69 one = round(one, word(at));
70 two = round(two, word(at + 8));
71 three = round(three, word(at + 16));
72 four = round(four, word(at + 24));
73 at += 32;
74 }
75 let combined = one
76 .rotate_left(1)
77 .wrapping_add(two.rotate_left(7))
78 .wrapping_add(three.rotate_left(12))
79 .wrapping_add(four.rotate_left(18));
80 merge(merge(merge(merge(combined, one), two), three), four)
81 } else {
82 P5
83 };
84 hash = hash.wrapping_add(bytes.len() as u64);
85 while at + 8 <= bytes.len() {
86 hash ^= round(0, word(at));
87 hash = hash.rotate_left(27).wrapping_mul(P1).wrapping_add(P4);
88 at += 8;
89 }
90 if at + 4 <= bytes.len() {
91 let tail = u32::from_le_bytes(bytes[at..at + 4].try_into().expect("four checksum bytes"));
92 hash ^= u64::from(tail).wrapping_mul(P1);
93 hash = hash.rotate_left(23).wrapping_mul(P2).wrapping_add(P3);
94 at += 4;
95 }
96 while at < bytes.len() {
97 hash ^= u64::from(bytes[at]).wrapping_mul(P5);
98 hash = hash.rotate_left(11).wrapping_mul(P1);
99 at += 1;
100 }
101 hash ^= hash >> 33;
102 hash = hash.wrapping_mul(P2);
103 hash ^= hash >> 29;
104 hash = hash.wrapping_mul(P3);
105 hash ^ (hash >> 32)
106}
107
108#[derive(Debug, Clone, Copy)]
109struct Slot {
110 offset: u64,
111 length: u32,
112 generation: u64,
113 hash: u64,
114}
115
116impl Slot {
117 fn bytes(self) -> [u8; SLOT_BYTES] {
118 let mut result = [0; SLOT_BYTES];
119 result[..8].copy_from_slice(&self.offset.to_le_bytes());
120 result[8..12].copy_from_slice(&self.length.to_le_bytes());
121 result[12..20].copy_from_slice(&self.generation.to_le_bytes());
122 result[20..28].copy_from_slice(&self.hash.to_le_bytes());
123 result
124 }
125
126 fn read(bytes: &[u8]) -> Self {
127 Self {
128 offset: u64::from_le_bytes(bytes[..8].try_into().expect("eight bytes")),
129 length: u32::from_le_bytes(bytes[8..12].try_into().expect("four bytes")),
130 generation: u64::from_le_bytes(bytes[12..20].try_into().expect("eight bytes")),
131 hash: u64::from_le_bytes(bytes[20..28].try_into().expect("eight bytes")),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy)]
137struct Page {
138 offset: u64,
139 length: u32,
140 hash: u64,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144enum FrequencyValue {
145 Null,
146 Integer(i128),
147 Code(u32),
148}
149
150#[derive(Debug, Clone)]
151struct FrequencyEntry {
152 value: FrequencyValue,
153 count: u64,
154}
155
156#[derive(Debug, Clone)]
161struct FrequencySummary {
162 entries: Vec<FrequencyEntry>,
163 omitted_max: u64,
164 ordinals: Vec<u64>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct FrequencyOccurrences {
170 pub omitted_max: u64,
172 pub ordinals: Vec<u64>,
174}
175
176#[derive(Debug, Clone)]
178pub struct Stripe {
179 rows: usize,
180 pages: Vec<Page>,
181 memberships: Vec<Option<Page>>,
182 zone: Zone,
183}
184
185impl Stripe {
186 #[must_use]
188 pub fn rows(&self) -> usize {
189 self.rows
190 }
191}
192
193#[derive(Debug, Clone)]
195pub struct Table {
196 name: String,
197 fields: Vec<Field>,
198 stripes: Vec<Stripe>,
199 rows: usize,
200 dictionaries: Vec<Option<Page>>,
201 frequencies: Vec<Option<FrequencySummary>>,
202}
203
204impl Table {
205 #[must_use]
207 pub fn name(&self) -> &str {
208 &self.name
209 }
210
211 #[must_use]
213 pub fn fields(&self) -> &[Field] {
214 &self.fields
215 }
216
217 #[must_use]
219 pub fn rows(&self) -> usize {
220 self.rows
221 }
222
223 #[must_use]
225 pub fn stripes(&self) -> &[Stripe] {
226 &self.stripes
227 }
228}
229
230#[derive(Debug)]
232struct GlobalDictionary {
233 primary: HashMap<u64, u32>,
234 collisions: HashMap<u64, Vec<u32>>,
235 offsets: Vec<u32>,
236 payload: Vec<u8>,
237 counts: Vec<u64>,
238 nulls: u64,
239}
240
241impl GlobalDictionary {
242 fn new() -> Self {
243 Self {
244 primary: HashMap::new(),
245 collisions: HashMap::new(),
246 offsets: vec![0],
247 payload: Vec::new(),
248 counts: Vec::new(),
249 nulls: 0,
250 }
251 }
252
253 fn bytes(&self, code: u32) -> Option<&[u8]> {
254 let start = *self.offsets.get(code as usize)? as usize;
255 let end = *self.offsets.get(code as usize + 1)? as usize;
256 self.payload.get(start..end)
257 }
258
259 fn code(&mut self, text: &str) -> Result<u32> {
260 let hash = checksum(text.as_bytes());
261 if let Some(&code) = self.primary.get(&hash) {
262 if self.bytes(code) == Some(text.as_bytes()) {
263 return Ok(code);
264 }
265 if let Some(codes) = self.collisions.get(&hash) {
266 if let Some(code) =
267 codes.iter().copied().find(|&code| self.bytes(code) == Some(text.as_bytes()))
268 {
269 return Ok(code);
270 }
271 }
272 let code = self.insert(text)?;
273 self.collisions.entry(hash).or_default().push(code);
274 return Ok(code);
275 }
276 let code = self.insert(text)?;
277 self.primary.insert(hash, code);
278 Ok(code)
279 }
280
281 fn insert(&mut self, text: &str) -> Result<u32> {
282 let code = u32::try_from(self.offsets.len() - 1)
283 .map_err(|_| invalid("global dictionary has too many values"))?;
284 self.payload.extend_from_slice(text.as_bytes());
285 self.offsets.push(
286 u32::try_from(self.payload.len())
287 .map_err(|_| invalid("global dictionary payload exceeds 4 GiB"))?,
288 );
289 self.counts.push(0);
290 Ok(code)
291 }
292
293 fn observe(&mut self, code: u32, null: bool) -> Result<()> {
294 if null {
295 self.nulls = self.nulls.saturating_add(1);
296 return Ok(());
297 }
298 let count = self
299 .counts
300 .get_mut(code as usize)
301 .ok_or_else(|| invalid("global dictionary count code is out of range"))?;
302 *count = count.saturating_add(1);
303 Ok(())
304 }
305}
306
307#[derive(Debug)]
309pub struct Writer {
310 file: File,
311 table: Table,
312 generation: u64,
313 order: Vec<(u64, u64)>,
314 next_order: u64,
315 dictionaries: Vec<Option<GlobalDictionary>>,
316 pending: Vec<PendingStripe>,
317}
318
319#[derive(Debug)]
320struct PendingStripe {
321 order: (u64, u64),
322 rows: usize,
323 pages: Vec<Vec<u8>>,
324 memberships: Vec<Option<Vec<u8>>>,
325 zone: Zone,
326}
327
328const EXTENT_STRIPES: usize = 32;
329
330impl Writer {
331 pub fn create(
337 path: impl AsRef<Path>,
338 name: impl Into<String>,
339 fields: Vec<Field>,
340 ) -> Result<Self> {
341 for field in &fields {
342 type_tag(&field.ty)?;
343 }
344 let mut file =
345 OpenOptions::new().write(true).read(true).create_new(true).open(path).map_err(io)?;
346 let mut header = [0; HEADER as usize];
347 header[..8].copy_from_slice(MAGIC);
348 header[8..12].copy_from_slice(&8_u32.to_le_bytes());
349 file.write_all(&header).map_err(io)?;
350 Ok(Self {
351 file,
352 dictionaries: fields
353 .iter()
354 .map(|field| (field.ty == LogicalType::Varchar).then(GlobalDictionary::new))
355 .collect(),
356 table: Table {
357 name: name.into(),
358 dictionaries: vec![None; fields.len()],
359 fields,
360 stripes: Vec::new(),
361 rows: 0,
362 frequencies: Vec::new(),
363 },
364 generation: 1,
365 order: Vec::new(),
366 next_order: 0,
367 pending: Vec::with_capacity(EXTENT_STRIPES),
368 })
369 }
370
371 pub fn append(&mut self, chunk: &Chunk) -> Result<()> {
377 let order = (self.next_order, 0);
378 self.next_order = self.next_order.saturating_add(1);
379 self.append_at(order, chunk)
380 }
381
382 pub fn append_at(&mut self, order: (u64, u64), chunk: &Chunk) -> Result<()> {
392 if chunk.is_empty() {
393 return Ok(());
394 }
395 if chunk.width() != self.table.fields.len() {
396 return Err(invalid("chunk width differs from table schema"));
397 }
398 let mut pages = Vec::with_capacity(chunk.width());
399 let mut memberships = Vec::with_capacity(chunk.width());
400 for (index, field) in self.table.fields.iter().enumerate() {
401 let column = chunk.column(index)?;
402 if column.logical_type() != &field.ty {
403 return Err(invalid("chunk type differs from table schema"));
404 }
405 let (bytes, membership) = encode(column, self.dictionaries[index].as_mut())?;
406 if bytes.len() > MAX_PAGE {
407 return Err(invalid("column page exceeds the configured bound"));
408 }
409 pages.push(bytes);
410 memberships.push(membership);
411 }
412 self.table.rows = self
413 .table
414 .rows
415 .checked_add(chunk.len())
416 .ok_or_else(|| invalid("row count overflow"))?;
417 self.pending.push(PendingStripe {
418 order,
419 rows: chunk.len(),
420 pages,
421 memberships,
422 zone: Zone::of(chunk),
423 });
424 if self.pending.len() == EXTENT_STRIPES {
425 self.flush_pending()?;
426 }
427 Ok(())
428 }
429
430 fn flush_pending(&mut self) -> Result<()> {
432 if self.pending.is_empty() {
433 return Ok(());
434 }
435 let width = self.table.fields.len();
436 let mut pages = vec![Vec::with_capacity(width); self.pending.len()];
437 let mut memberships = vec![vec![None; width]; self.pending.len()];
438 for column in 0..width {
439 for (stripe, pending) in self.pending.iter().enumerate() {
440 let bytes = &pending.pages[column];
441 let offset = self.file.stream_position().map_err(io)?;
442 self.file.write_all(bytes).map_err(io)?;
443 pages[stripe].push(Page {
444 offset,
445 length: u32::try_from(bytes.len())
446 .map_err(|_| invalid("page length overflow"))?,
447 hash: checksum(bytes),
448 });
449 }
450 for (stripe, pending) in self.pending.iter().enumerate() {
451 let Some(bytes) = &pending.memberships[column] else { continue };
452 let offset = self.file.stream_position().map_err(io)?;
453 self.file.write_all(bytes).map_err(io)?;
454 *memberships[stripe]
455 .get_mut(column)
456 .ok_or_else(|| invalid("membership column is missing"))? = Some(Page {
457 offset,
458 length: u32::try_from(bytes.len())
459 .map_err(|_| invalid("membership page length overflow"))?,
460 hash: checksum(bytes),
461 });
462 }
463 }
464 for ((pending, pages), memberships) in self.pending.drain(..).zip(pages).zip(memberships) {
465 self.table.stripes.push(Stripe {
466 rows: pending.rows,
467 pages,
468 memberships,
469 zone: pending.zone,
470 });
471 self.order.push(pending.order);
472 }
473 Ok(())
474 }
475
476 fn numeric_frequency(&self, column: usize) -> Result<Option<FrequencySummary>> {
480 let ty = &self.table.fields[column].ty;
481 if !matches!(
482 ty,
483 LogicalType::TinyInt
484 | LogicalType::SmallInt
485 | LogicalType::Integer
486 | LogicalType::BigInt
487 | LogicalType::UTinyInt
488 | LogicalType::USmallInt
489 | LogicalType::UInteger
490 | LogicalType::UBigInt
491 | LogicalType::Date
492 | LogicalType::Timestamp
493 ) {
494 return Ok(None);
495 }
496 let mut candidates: HashMap<FrequencyValue, u32> = HashMap::new();
497 let mut decrements = 0_u64;
498 self.visit_numeric(column, |_, value| {
499 if let Some(count) = candidates.get_mut(&value) {
500 *count = count.saturating_add(1);
501 } else if candidates.len() < FREQUENCY_CANDIDATES {
502 candidates.insert(value, 1);
503 } else {
504 candidates.retain(|_, count| {
505 *count -= 1;
506 *count != 0
507 });
508 decrements = decrements.saturating_add(1);
509 }
510 })?;
511 let (exact, ordinals) = if decrements == 0 {
512 (
513 candidates
514 .into_iter()
515 .map(|(value, count)| (value, u64::from(count)))
516 .collect::<HashMap<_, _>>(),
517 Vec::new(),
518 )
519 } else {
520 let mut lower = candidates.values().copied().collect::<Vec<_>>();
521 lower.sort_unstable_by(|left, right| right.cmp(left));
522 if lower.len() < FREQUENCY_BUILD_RANK
523 || u64::from(lower[FREQUENCY_BUILD_RANK - 1]) <= decrements
524 {
525 return Ok(None);
526 }
527 let mut exact =
528 candidates.into_keys().map(|value| (value, 0_u64)).collect::<HashMap<_, _>>();
529 let mut ordinals = Vec::new();
530 let mut exceeded = false;
531 self.visit_numeric(column, |ordinal, value| {
532 if let Some(count) = exact.get_mut(&value) {
533 *count = count.saturating_add(1);
534 if !exceeded {
535 if ordinals.len() < FREQUENCY_ORDINALS {
536 ordinals.push(ordinal);
537 } else {
538 ordinals.clear();
539 exceeded = true;
540 }
541 }
542 }
543 })?;
544 (exact, ordinals)
545 };
546 let mut entries = exact
547 .into_iter()
548 .map(|(value, count)| FrequencyEntry { value, count })
549 .collect::<Vec<_>>();
550 entries.sort_unstable_by(|left, right| {
551 right.count.cmp(&left.count).then_with(|| frequency_order(left.value, right.value))
552 });
553 let omitted_max =
554 entries.get(FREQUENCY_ENTRIES).map_or(decrements, |entry| decrements.max(entry.count));
555 entries.truncate(FREQUENCY_ENTRIES);
556 Ok(Some(FrequencySummary { entries, omitted_max, ordinals }))
557 }
558
559 fn visit_numeric(
560 &self,
561 column: usize,
562 mut visit: impl FnMut(u64, FrequencyValue),
563 ) -> Result<()> {
564 let ty = &self.table.fields[column].ty;
565 let mut start = 0_u64;
566 for stripe in &self.table.stripes {
567 let page = stripe.pages[column];
568 let mut bytes = vec![0; page.length as usize];
569 read_at(&self.file, page.offset, &mut bytes)?;
570 if checksum(&bytes) != page.hash {
571 return Err(invalid("column page checksum differs while building frequencies"));
572 }
573 let vector = decode(ty, stripe.rows, &bytes, None)?;
574 for row in 0..stripe.rows {
576 let value = if vector.is_null_at(row) {
577 FrequencyValue::Null
578 } else {
579 let widened = match vector.signed_at(row) {
583 Some(value) => Some(value),
584 None => match vector.value_at(row) {
585 Value::UTinyInt(value) => Some(i128::from(value)),
586 Value::USmallInt(value) => Some(i128::from(value)),
587 Value::UInteger(value) => Some(i128::from(value)),
588 Value::UBigInt(value) => Some(i128::from(value)),
589 _ => None,
590 },
591 };
592 FrequencyValue::Integer(widened.ok_or_else(|| {
593 invalid("numeric frequency page did not contain an integer value")
594 })?)
595 };
596 visit(start.saturating_add(row as u64), value);
597 }
598 start = start.saturating_add(stripe.rows as u64);
599 }
600 Ok(())
601 }
602
603 fn numeric_frequencies(&self) -> Result<Vec<Option<FrequencySummary>>> {
605 let columns = self
606 .table
607 .fields
608 .iter()
609 .enumerate()
610 .filter_map(|(column, field)| {
611 matches!(
612 field.ty,
613 LogicalType::TinyInt
614 | LogicalType::SmallInt
615 | LogicalType::Integer
616 | LogicalType::BigInt
617 | LogicalType::UTinyInt
618 | LogicalType::USmallInt
619 | LogicalType::UInteger
620 | LogicalType::UBigInt
621 | LogicalType::Date
622 | LogicalType::Timestamp
623 )
624 .then_some(column)
625 })
626 .collect::<Vec<_>>();
627 let workers = std::thread::available_parallelism()
628 .map_or(1, usize::from)
629 .min(MAX_FREQUENCY_WORKERS)
630 .min(columns.len());
631 if workers <= 1 {
632 let mut frequencies = vec![None; self.table.fields.len()];
633 for column in columns {
634 frequencies[column] = self.numeric_frequency(column)?;
635 }
636 return Ok(frequencies);
637 }
638 let width = columns.len().div_ceil(workers);
639 let pieces = std::thread::scope(|scope| {
640 columns
641 .chunks(width)
642 .map(|columns| {
643 scope.spawn(|| {
644 columns
645 .iter()
646 .map(|&column| Ok((column, self.numeric_frequency(column)?)))
647 .collect::<Result<Vec<_>>>()
648 })
649 })
650 .collect::<Vec<_>>()
651 .into_iter()
652 .map(|handle| {
653 handle
654 .join()
655 .map_err(|_| Error::internal("a native frequency worker panicked"))?
656 })
657 .collect::<Result<Vec<_>>>()
658 })?;
659 let mut frequencies = vec![None; self.table.fields.len()];
660 for piece in pieces {
661 for (column, summary) in piece {
662 frequencies[column] = summary;
663 }
664 }
665 Ok(frequencies)
666 }
667
668 pub fn finish(mut self) -> Result<Table> {
674 self.flush_pending()?;
675 let mut stripes = std::mem::take(&mut self.order)
676 .into_iter()
677 .zip(std::mem::take(&mut self.table.stripes))
678 .collect::<Vec<_>>();
679 stripes.sort_by_key(|(order, _)| *order);
680 self.table.stripes = stripes.into_iter().map(|(_, stripe)| stripe).collect();
681 self.table.frequencies = self.numeric_frequencies()?;
682 for (index, dictionary) in self.dictionaries.into_iter().enumerate() {
683 let Some(dictionary) = dictionary else { continue };
684 self.table.frequencies[index] = Some(code_frequency(&dictionary));
685 let encoded = encode_global_dictionary(dictionary)?;
686 let offset = self.file.stream_position().map_err(io)?;
687 self.file.write_all(&encoded.index).map_err(io)?;
688 self.file.write_all(&encoded.payload).map_err(io)?;
689 let length = encoded
690 .index
691 .len()
692 .checked_add(encoded.payload.len())
693 .ok_or_else(|| invalid("dictionary page length overflow"))?;
694 self.table.dictionaries[index] = Some(Page {
695 offset,
696 length: u32::try_from(length)
697 .map_err(|_| invalid("dictionary page length overflow"))?,
698 hash: checksum(&encoded.index),
699 });
700 }
701 let directory = encode_directory(&self.table)?;
702 if directory.len() > MAX_DIRECTORY {
703 return Err(invalid("directory exceeds the configured bound"));
704 }
705 let offset = self.file.stream_position().map_err(io)?;
706 self.file.write_all(&directory).map_err(io)?;
707 self.file.sync_all().map_err(io)?;
708 let slot = Slot {
709 offset,
710 length: u32::try_from(directory.len())
711 .map_err(|_| invalid("directory length overflow"))?,
712 generation: self.generation,
713 hash: checksum(&directory),
714 };
715 self.file.seek(SeekFrom::Start(16)).map_err(io)?;
716 self.file.write_all(&slot.bytes()).map_err(io)?;
717 self.file.sync_all().map_err(io)?;
718 Ok(self.table)
719 }
720}
721
722#[derive(Debug, Clone)]
724pub struct Reader {
725 file: Arc<File>,
726 table: Arc<Table>,
727 dictionaries: Arc<Vec<OnceLock<Arc<Vector>>>>,
728 extents: Arc<Vec<Vec<ExtentPart>>>,
729 extent_cache: Arc<Vec<Mutex<Vec<CachedExtent>>>>,
730}
731
732#[derive(Debug, Clone, Copy, Default)]
733struct ExtentPart {
734 offset: u64,
735 length: usize,
736 page_start: usize,
737}
738
739#[derive(Debug)]
740struct CachedExtent {
741 offset: u64,
742 bytes: Arc<Vec<u8>>,
743}
744
745const CACHED_EXTENTS_PER_COLUMN: usize = 8;
746
747type CrossingCache = OnceLock<Box<[OnceLock<Result<Vec<u8>>>]>>;
748
749#[derive(Debug)]
750struct NativeText {
751 file: Arc<File>,
752 offsets: Vec<u32>,
753 payload: u64,
754 payload_len: usize,
755 hashes: Vec<u64>,
756 payload_blocks: Vec<OnceLock<Result<Vec<u8>>>>,
757 crossing: Vec<CrossingCache>,
758}
759
760const TEXT_PAYLOAD_BLOCK: usize = 64 * 1024;
761const TEXT_CROSSING_BLOCK: usize = 1024;
762
763impl NativeText {
764 fn payload_block(&self, block: usize) -> Result<Option<&[u8]>> {
765 let Some(slot) = self.payload_blocks.get(block) else { return Ok(None) };
766 slot.get_or_init(|| {
767 let start = block
768 .checked_mul(TEXT_PAYLOAD_BLOCK)
769 .ok_or_else(|| invalid("global dictionary block offset overflow"))?;
770 let len = TEXT_PAYLOAD_BLOCK.min(
771 self.payload_len
772 .checked_sub(start)
773 .ok_or_else(|| invalid("global dictionary block starts past payload"))?,
774 );
775 let mut bytes = vec![0; len];
776 read_at(&self.file, self.payload + start as u64, &mut bytes)?;
777 if checksum(&bytes)
778 != *self
779 .hashes
780 .get(block)
781 .ok_or_else(|| invalid("global dictionary block has no checksum"))?
782 {
783 return Err(invalid("global dictionary payload checksum differs"));
784 }
785 Ok(bytes)
786 })
787 .as_ref()
788 .map(|bytes| Some(bytes.as_slice()))
789 .map_err(Clone::clone)
790 }
791}
792
793impl TextSource for NativeText {
794 fn len(&self) -> usize {
795 self.offsets.len().saturating_sub(1)
796 }
797
798 fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
799 let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
800 else {
801 return Ok(None);
802 };
803 if start == end {
804 return Ok(Some(&[]));
805 }
806 let first = start as usize / TEXT_PAYLOAD_BLOCK;
807 let last = (end as usize - 1) / TEXT_PAYLOAD_BLOCK;
808 if first == last {
809 let Some(block) = self.payload_block(first)? else { return Ok(None) };
810 let within = start as usize % TEXT_PAYLOAD_BLOCK;
811 return Ok(block.get(within..within + (end - start) as usize));
812 }
813 let Some(crossing) = self.crossing.get(index / TEXT_CROSSING_BLOCK) else {
814 return Ok(None);
815 };
816 let block = crossing.get_or_init(|| {
817 (0..TEXT_CROSSING_BLOCK).map(|_| OnceLock::new()).collect::<Vec<_>>().into_boxed_slice()
818 });
819 block[index % TEXT_CROSSING_BLOCK]
820 .get_or_init(|| {
821 let mut bytes = Vec::with_capacity((end - start) as usize);
822 for part in first..=last {
823 let source = self
824 .payload_block(part)?
825 .ok_or_else(|| invalid("global dictionary block is missing"))?;
826 let from = if part == first { start as usize % TEXT_PAYLOAD_BLOCK } else { 0 };
827 let to = if part == last {
828 (end as usize - 1) % TEXT_PAYLOAD_BLOCK + 1
829 } else {
830 source.len()
831 };
832 bytes.extend_from_slice(source.get(from..to).ok_or_else(|| {
833 invalid("global dictionary value exceeds its payload block")
834 })?);
835 }
836 Ok(bytes)
837 })
838 .as_ref()
839 .map(|bytes| Some(bytes.as_slice()))
840 .map_err(Clone::clone)
841 }
842
843 fn bytes_len_at(&self, index: usize) -> Result<Option<usize>> {
844 let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
845 else {
846 return Ok(None);
847 };
848 Ok(Some((end - start) as usize))
849 }
850
851 fn footprint(&self) -> usize {
852 self.offsets.capacity() * size_of::<u32>()
853 + self.payload_blocks.capacity() * size_of::<OnceLock<Result<Vec<u8>>>>()
854 + self.hashes.capacity() * size_of::<u64>()
855 + self
856 .payload_blocks
857 .iter()
858 .filter_map(OnceLock::get)
859 .filter_map(|result| result.as_ref().ok())
860 .map(Vec::capacity)
861 .sum::<usize>()
862 + self.crossing.capacity() * size_of::<CrossingCache>()
863 + self
864 .crossing
865 .iter()
866 .filter_map(OnceLock::get)
867 .map(|block| {
868 block.len() * size_of::<OnceLock<Result<Vec<u8>>>>()
869 + block
870 .iter()
871 .filter_map(OnceLock::get)
872 .filter_map(|result| result.as_ref().ok())
873 .map(Vec::capacity)
874 .sum::<usize>()
875 })
876 .sum::<usize>()
877 }
878}
879
880fn extent_parts(table: &Table) -> Result<Vec<Vec<ExtentPart>>> {
882 let mut refs = Vec::with_capacity(table.stripes.len().saturating_mul(table.fields.len()));
883 for (stripe, entry) in table.stripes.iter().enumerate() {
884 for (column, page) in entry.pages.iter().enumerate() {
885 refs.push((page.offset, column, stripe, page.length as usize));
886 }
887 }
888 refs.sort_unstable_by_key(|entry| entry.0);
889 let mut parts = vec![vec![ExtentPart::default(); table.fields.len()]; table.stripes.len()];
890 let mut first = 0;
891 while first < refs.len() {
892 let (offset, column, _, first_len) = refs[first];
893 let mut end = offset
894 .checked_add(first_len as u64)
895 .ok_or_else(|| invalid("column extent range overflow"))?;
896 let mut last = first + 1;
897 while last < refs.len()
898 && last - first < EXTENT_STRIPES
899 && refs[last].1 == column
900 && refs[last].0 == end
901 {
902 end = end
903 .checked_add(refs[last].3 as u64)
904 .ok_or_else(|| invalid("column extent range overflow"))?;
905 last += 1;
906 }
907 let length = usize::try_from(end - offset)
908 .map_err(|_| invalid("column extent length exceeds this platform"))?;
909 for &(_, _, stripe, _) in &refs[first..last] {
910 let page = table.stripes[stripe].pages[column];
911 let page_start = usize::try_from(page.offset - offset)
912 .map_err(|_| invalid("column page offset exceeds this platform"))?;
913 parts[stripe][column] = ExtentPart { offset, length, page_start };
914 }
915 first = last;
916 }
917 Ok(parts)
918}
919
920impl Reader {
921 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
927 let mut file = File::open(path).map_err(io)?;
928 let size = file.metadata().map_err(io)?.len();
929 if size < HEADER {
930 return Err(invalid("file is shorter than its header"));
931 }
932 let mut header = [0; HEADER as usize];
933 file.read_exact(&mut header).map_err(io)?;
934 let version = u32::from_le_bytes([header[8], header[9], header[10], header[11]]);
935 if !((&header[..8] == MAGIC && version == 8) || (&header[..8] == MAGIC_V7 && version == 7))
936 {
937 return Err(invalid("magic or major version is unsupported"));
938 }
939 let mut selected = None;
940 for start in [16, 16 + SLOT_BYTES] {
941 let slot = Slot::read(&header[start..start + SLOT_BYTES]);
942 if slot.generation == 0 || slot.length == 0 || slot.length as usize > MAX_DIRECTORY {
943 continue;
944 }
945 let Some(end) = slot.offset.checked_add(u64::from(slot.length)) else { continue };
946 if slot.offset < HEADER || end > size {
947 continue;
948 }
949 let mut bytes = vec![0; slot.length as usize];
950 file.seek(SeekFrom::Start(slot.offset)).map_err(io)?;
951 file.read_exact(&mut bytes).map_err(io)?;
952 if checksum(&bytes) == slot.hash
953 && selected
954 .as_ref()
955 .is_none_or(|(old, _): &(Slot, Vec<u8>)| old.generation < slot.generation)
956 {
957 selected = Some((slot, bytes));
958 }
959 }
960 let (_, bytes) = selected.ok_or_else(|| invalid("no committed directory slot is valid"))?;
961 let table = decode_directory(&bytes, size, version)?;
962 let extents = extent_parts(&table)?;
963 let dictionaries = (0..table.fields.len()).map(|_| OnceLock::new()).collect();
964 let extent_cache = (0..table.fields.len())
965 .map(|_| Mutex::new(Vec::with_capacity(CACHED_EXTENTS_PER_COLUMN)))
966 .collect::<Vec<_>>();
967 Ok(Self {
968 file: Arc::new(file),
969 table: Arc::new(table),
970 dictionaries: Arc::new(dictionaries),
971 extents: Arc::new(extents),
972 extent_cache: Arc::new(extent_cache),
973 })
974 }
975
976 #[must_use]
978 pub fn table(&self) -> &Table {
979 &self.table
980 }
981
982 pub fn top_frequencies(&self, column: usize, top: usize) -> Result<Option<Vec<(Value, u64)>>> {
991 let field = self
992 .table
993 .fields
994 .get(column)
995 .ok_or_else(|| invalid("frequency column index out of range"))?;
996 let Some(summary) = self.table.frequencies.get(column).and_then(Option::as_ref) else {
997 return Ok(None);
998 };
999 if top == 0 || summary.entries.len() < top {
1000 return Ok(None);
1001 }
1002 let boundary = summary.entries[top - 1].count;
1003 if boundary <= summary.omitted_max {
1004 return Ok(None);
1005 }
1006 let dictionary =
1007 if field.ty == LogicalType::Varchar { self.dictionary(column)? } else { None };
1008 let mut out = Vec::with_capacity(summary.entries.len());
1009 for entry in &summary.entries {
1010 let value = match entry.value {
1011 FrequencyValue::Null => Value::Null,
1012 FrequencyValue::Integer(value) => match field.ty {
1013 LogicalType::TinyInt => Value::TinyInt(
1014 i8::try_from(value)
1015 .map_err(|_| invalid("frequency TINYINT is out of range"))?,
1016 ),
1017 LogicalType::UTinyInt => Value::UTinyInt(
1018 u8::try_from(value)
1019 .map_err(|_| invalid("frequency UTINYINT is out of range"))?,
1020 ),
1021 LogicalType::USmallInt => Value::USmallInt(
1022 u16::try_from(value)
1023 .map_err(|_| invalid("frequency USMALLINT is out of range"))?,
1024 ),
1025 LogicalType::UInteger => Value::UInteger(
1026 u32::try_from(value)
1027 .map_err(|_| invalid("frequency UINTEGER is out of range"))?,
1028 ),
1029 LogicalType::UBigInt => Value::UBigInt(
1030 u64::try_from(value)
1031 .map_err(|_| invalid("frequency UBIGINT is out of range"))?,
1032 ),
1033 LogicalType::SmallInt => Value::SmallInt(
1034 i16::try_from(value)
1035 .map_err(|_| invalid("frequency SMALLINT is out of range"))?,
1036 ),
1037 LogicalType::Integer => Value::Integer(
1038 i32::try_from(value)
1039 .map_err(|_| invalid("frequency INTEGER is out of range"))?,
1040 ),
1041 LogicalType::BigInt => Value::BigInt(
1042 i64::try_from(value)
1043 .map_err(|_| invalid("frequency BIGINT is out of range"))?,
1044 ),
1045 LogicalType::Date => Value::Date(
1046 i32::try_from(value)
1047 .map_err(|_| invalid("frequency DATE is out of range"))?,
1048 ),
1049 LogicalType::Timestamp => Value::Timestamp(
1050 i64::try_from(value)
1051 .map_err(|_| invalid("frequency TIMESTAMP is out of range"))?,
1052 ),
1053 _ => return Err(invalid("integer frequency belongs to another type")),
1054 },
1055 FrequencyValue::Code(code) => dictionary
1056 .as_ref()
1057 .ok_or_else(|| invalid("frequency code has no dictionary"))?
1058 .try_value_at(code as usize)?,
1059 };
1060 out.push((value, entry.count));
1061 }
1062 Ok(Some(out))
1063 }
1064
1065 pub fn frequency_occurrences(&self, column: usize) -> Result<Option<FrequencyOccurrences>> {
1075 self.table
1076 .fields
1077 .get(column)
1078 .ok_or_else(|| invalid("frequency column index out of range"))?;
1079 let Some(summary) = self.table.frequencies.get(column).and_then(Option::as_ref) else {
1080 return Ok(None);
1081 };
1082 if summary.ordinals.is_empty() {
1083 return Ok(None);
1084 }
1085 Ok(Some(FrequencyOccurrences {
1086 omitted_max: summary.omitted_max,
1087 ordinals: summary.ordinals.clone(),
1088 }))
1089 }
1090
1091 fn dictionary(&self, column: usize) -> Result<Option<Arc<Vector>>> {
1092 let Some(page) = self.table.dictionaries[column] else { return Ok(None) };
1093 if let Some(dictionary) = self.dictionaries[column].get() {
1094 return Ok(Some(Arc::clone(dictionary)));
1095 }
1096 let dictionary = Arc::new(open_global_dictionary(
1097 Arc::clone(&self.file),
1098 page,
1099 &self.table.fields[column].ty,
1100 )?);
1101 let _ = self.dictionaries[column].set(Arc::clone(&dictionary));
1102 Ok(Some(self.dictionaries[column].get().map_or(dictionary, Arc::clone)))
1103 }
1104
1105 pub fn read(&self, stripe: usize, columns: &[usize]) -> Result<Chunk> {
1111 self.read_impl(stripe, columns, true)
1112 }
1113
1114 pub fn read_sparse(&self, stripe: usize, columns: &[usize]) -> Result<Chunk> {
1123 self.read_impl(stripe, columns, false)
1124 }
1125
1126 pub fn skips_codes(&self, stripe: usize, column: usize, candidates: &[u32]) -> Result<bool> {
1135 if candidates.is_empty() {
1136 return Ok(true);
1137 }
1138 if candidates.windows(2).any(|pair| pair[0] >= pair[1]) {
1139 return Err(Error::internal("native code candidates are not sorted and unique"));
1140 }
1141 let stripe =
1142 self.table.stripes.get(stripe).ok_or_else(|| invalid("stripe index out of range"))?;
1143 let Some(page) = stripe.memberships.get(column).copied().flatten() else {
1144 return Ok(false);
1145 };
1146 let mut bytes = vec![0; page.length as usize];
1147 read_at(&self.file, page.offset, &mut bytes)?;
1148 if checksum(&bytes) != page.hash {
1149 return Err(invalid("membership page checksum differs"));
1150 }
1151 let codes = decode_membership(&bytes)?;
1152 let mut left = 0;
1153 let mut right = 0;
1154 while left < codes.len() && right < candidates.len() {
1155 match codes[left].cmp(&candidates[right]) {
1156 Ordering::Less => left += 1,
1157 Ordering::Greater => right += 1,
1158 Ordering::Equal => return Ok(false),
1159 }
1160 }
1161 Ok(true)
1162 }
1163
1164 fn read_impl(&self, stripe: usize, columns: &[usize], prefetch: bool) -> Result<Chunk> {
1165 let stripe_index = stripe;
1166 let stripe =
1167 self.table.stripes.get(stripe).ok_or_else(|| invalid("stripe index out of range"))?;
1168 let mut picked = Vec::with_capacity(columns.len());
1169 for &column in columns {
1170 let field = self
1171 .table
1172 .fields
1173 .get(column)
1174 .ok_or_else(|| invalid("column index out of range"))?;
1175 let page = stripe.pages.get(column).ok_or_else(|| invalid("stripe page is missing"))?;
1176 let part = self
1177 .extents
1178 .get(stripe_index)
1179 .and_then(|parts| parts.get(column))
1180 .ok_or_else(|| invalid("column extent is missing"))?;
1181 let bytes = if !prefetch || part.length == page.length as usize {
1182 let mut bytes = vec![0; page.length as usize];
1183 read_at(&self.file, page.offset, &mut bytes)?;
1184 Arc::new(bytes)
1185 } else {
1186 let cached = self.extent_cache[column]
1187 .lock()
1188 .map_err(|_| invalid("column extent cache is poisoned"))?
1189 .iter()
1190 .find(|cached| cached.offset == part.offset)
1191 .map(|cached| Arc::clone(&cached.bytes));
1192 if let Some(bytes) = cached {
1193 bytes
1194 } else {
1195 let mut bytes = vec![0; part.length];
1196 read_at(&self.file, part.offset, &mut bytes)?;
1197 let bytes = Arc::new(bytes);
1198 let mut cache = self.extent_cache[column]
1199 .lock()
1200 .map_err(|_| invalid("column extent cache is poisoned"))?;
1201 if let Some(cached) = cache.iter().find(|cached| cached.offset == part.offset) {
1202 Arc::clone(&cached.bytes)
1203 } else {
1204 if cache.len() == CACHED_EXTENTS_PER_COLUMN {
1205 cache.remove(0);
1206 }
1207 cache.push(CachedExtent { offset: part.offset, bytes: Arc::clone(&bytes) });
1208 bytes
1209 }
1210 }
1211 };
1212 let page_start = if prefetch { part.page_start } else { 0 };
1213 let end = page_start
1214 .checked_add(page.length as usize)
1215 .ok_or_else(|| invalid("column page range overflow"))?;
1216 let page_bytes = bytes
1217 .get(page_start..end)
1218 .ok_or_else(|| invalid("column page exceeds its extent"))?;
1219 if checksum(page_bytes) != page.hash {
1220 return Err(invalid("column page checksum differs"));
1221 }
1222 let dictionary = self.dictionary(column)?;
1223 picked.push(decode(&field.ty, stripe.rows, page_bytes, dictionary)?);
1224 }
1225 Chunk::with_rows(picked, stripe.rows)
1226 }
1227
1228 #[must_use]
1230 pub fn skips(&self, stripe: usize, probes: &[Probe]) -> bool {
1231 self.table.stripes.get(stripe).is_some_and(|stripe| stripe.zone.skips(probes))
1232 }
1233}
1234
1235#[cfg(unix)]
1236fn read_at(file: &File, mut offset: u64, mut bytes: &mut [u8]) -> Result<()> {
1237 use std::os::unix::fs::FileExt;
1238 while !bytes.is_empty() {
1239 let read = file.read_at(bytes, offset).map_err(io)?;
1240 if read == 0 {
1241 return Err(invalid("column page ends before its declared length"));
1242 }
1243 offset += read as u64;
1244 bytes = &mut bytes[read..];
1245 }
1246 Ok(())
1247}
1248
1249#[cfg(not(unix))]
1250fn read_at(file: &File, offset: u64, bytes: &mut [u8]) -> Result<()> {
1251 let mut file = file.try_clone().map_err(io)?;
1252 file.seek(SeekFrom::Start(offset)).map_err(io)?;
1253 file.read_exact(bytes).map_err(io)
1254}
1255
1256fn type_tag(ty: &LogicalType) -> Result<u8> {
1257 match ty {
1258 LogicalType::SmallInt => Ok(1),
1259 LogicalType::Integer => Ok(2),
1260 LogicalType::BigInt => Ok(3),
1261 LogicalType::Varchar => Ok(4),
1262 LogicalType::Date => Ok(5),
1263 LogicalType::Timestamp => Ok(6),
1264 LogicalType::Boolean => Ok(7),
1265 LogicalType::TinyInt => Ok(8),
1266 LogicalType::UTinyInt => Ok(9),
1267 LogicalType::USmallInt => Ok(10),
1268 LogicalType::UInteger => Ok(11),
1269 LogicalType::UBigInt => Ok(12),
1270 _ => Err(Error::not_implemented(format!("native storage for {ty}"))),
1271 }
1272}
1273
1274fn tag_type(tag: u8) -> Result<LogicalType> {
1275 match tag {
1276 1 => Ok(LogicalType::SmallInt),
1277 2 => Ok(LogicalType::Integer),
1278 3 => Ok(LogicalType::BigInt),
1279 4 => Ok(LogicalType::Varchar),
1280 5 => Ok(LogicalType::Date),
1281 6 => Ok(LogicalType::Timestamp),
1282 7 => Ok(LogicalType::Boolean),
1283 8 => Ok(LogicalType::TinyInt),
1284 9 => Ok(LogicalType::UTinyInt),
1285 10 => Ok(LogicalType::USmallInt),
1286 11 => Ok(LogicalType::UInteger),
1287 12 => Ok(LogicalType::UBigInt),
1288 _ => Err(invalid("column type tag is unknown")),
1289 }
1290}
1291
1292fn put_u16(out: &mut Vec<u8>, value: u16) {
1293 out.extend_from_slice(&value.to_le_bytes());
1294}
1295fn put_u32(out: &mut Vec<u8>, value: u32) {
1296 out.extend_from_slice(&value.to_le_bytes());
1297}
1298fn put_u64(out: &mut Vec<u8>, value: u64) {
1299 out.extend_from_slice(&value.to_le_bytes());
1300}
1301fn put_var_u64(out: &mut Vec<u8>, mut value: u64) {
1302 while value >= 0x80 {
1303 out.push((value as u8 & 0x7f) | 0x80);
1304 value >>= 7;
1305 }
1306 out.push(value as u8);
1307}
1308
1309fn frequency_order(left: FrequencyValue, right: FrequencyValue) -> Ordering {
1310 match (left, right) {
1311 (FrequencyValue::Null, FrequencyValue::Null) => Ordering::Equal,
1312 (FrequencyValue::Null, _) => Ordering::Less,
1313 (_, FrequencyValue::Null) => Ordering::Greater,
1314 (FrequencyValue::Integer(left), FrequencyValue::Integer(right)) => left.cmp(&right),
1315 (FrequencyValue::Code(left), FrequencyValue::Code(right)) => left.cmp(&right),
1316 (FrequencyValue::Integer(_), FrequencyValue::Code(_)) => Ordering::Less,
1317 (FrequencyValue::Code(_), FrequencyValue::Integer(_)) => Ordering::Greater,
1318 }
1319}
1320
1321fn code_frequency(dictionary: &GlobalDictionary) -> FrequencySummary {
1322 let mut entries = dictionary
1323 .counts
1324 .iter()
1325 .enumerate()
1326 .filter(|(_, count)| **count != 0)
1327 .map(|(code, &count)| FrequencyEntry { value: FrequencyValue::Code(code as u32), count })
1328 .collect::<Vec<_>>();
1329 if dictionary.nulls != 0 {
1330 entries.push(FrequencyEntry { value: FrequencyValue::Null, count: dictionary.nulls });
1331 }
1332 entries.sort_unstable_by(|left, right| {
1333 right.count.cmp(&left.count).then_with(|| frequency_order(left.value, right.value))
1334 });
1335 let omitted_max = entries.get(FREQUENCY_ENTRIES).map_or(0, |entry| entry.count);
1336 entries.truncate(FREQUENCY_ENTRIES);
1337 FrequencySummary { entries, omitted_max, ordinals: Vec::new() }
1338}
1339
1340fn encode_directory(table: &Table) -> Result<Vec<u8>> {
1341 encode_directory_version(table, 8)
1342}
1343
1344fn encode_directory_version(table: &Table, version: u32) -> Result<Vec<u8>> {
1345 let mut out = if version == 7 { DIRECTORY_V7.to_vec() } else { DIRECTORY.to_vec() };
1346 let name = table.name.as_bytes();
1347 put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("table name too long"))?);
1348 out.extend_from_slice(name);
1349 put_u16(&mut out, u16::try_from(table.fields.len()).map_err(|_| invalid("too many columns"))?);
1350 for field in &table.fields {
1351 let name = field.name.as_bytes();
1352 put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("column name too long"))?);
1353 out.extend_from_slice(name);
1354 out.push(type_tag(&field.ty)?);
1355 out.push(u8::from(field.not_null));
1356 }
1357 for dictionary in &table.dictionaries {
1358 match dictionary {
1359 None => out.push(0),
1360 Some(page) => {
1361 out.push(1);
1362 put_u64(&mut out, page.offset);
1363 put_u32(&mut out, page.length);
1364 put_u64(&mut out, page.hash);
1365 }
1366 }
1367 }
1368 put_u64(&mut out, u64::try_from(table.rows).map_err(|_| invalid("row count overflow"))?);
1369 put_u32(&mut out, u32::try_from(table.stripes.len()).map_err(|_| invalid("too many stripes"))?);
1370 for stripe in &table.stripes {
1371 put_u32(
1372 &mut out,
1373 u32::try_from(stripe.rows).map_err(|_| invalid("stripe row count overflow"))?,
1374 );
1375 for page in &stripe.pages {
1376 put_u64(&mut out, page.offset);
1377 put_u32(&mut out, page.length);
1378 put_u64(&mut out, page.hash);
1379 }
1380 if version >= 8 {
1381 for (field, membership) in table.fields.iter().zip(&stripe.memberships) {
1382 if field.ty != LogicalType::Varchar {
1383 continue;
1384 }
1385 let page = membership
1386 .ok_or_else(|| invalid("string page has no code membership index"))?;
1387 put_u64(&mut out, page.offset);
1388 put_u32(&mut out, page.length);
1389 put_u64(&mut out, page.hash);
1390 }
1391 }
1392 for range in stripe.zone.columns() {
1393 put_bound(&mut out, range.low.as_ref())?;
1394 put_bound(&mut out, range.high.as_ref())?;
1395 put_u32(
1396 &mut out,
1397 u32::try_from(range.nulls).map_err(|_| invalid("null count overflow"))?,
1398 );
1399 }
1400 }
1401 out.extend_from_slice(FREQUENCIES);
1402 put_u16(
1403 &mut out,
1404 u16::try_from(table.frequencies.len())
1405 .map_err(|_| invalid("too many frequency columns"))?,
1406 );
1407 for summary in &table.frequencies {
1408 let Some(summary) = summary else {
1409 out.push(0);
1410 continue;
1411 };
1412 out.push(1);
1413 put_u64(&mut out, summary.omitted_max);
1414 put_u32(
1415 &mut out,
1416 u32::try_from(summary.entries.len())
1417 .map_err(|_| invalid("too many frequency entries"))?,
1418 );
1419 for entry in &summary.entries {
1420 match entry.value {
1421 FrequencyValue::Null => out.push(0),
1422 FrequencyValue::Integer(value) => {
1423 out.push(1);
1424 out.extend_from_slice(&value.to_le_bytes());
1425 }
1426 FrequencyValue::Code(value) => {
1427 out.push(2);
1428 put_u32(&mut out, value);
1429 }
1430 }
1431 put_u64(&mut out, entry.count);
1432 }
1433 put_u32(
1434 &mut out,
1435 u32::try_from(summary.ordinals.len())
1436 .map_err(|_| invalid("too many frequency ordinals"))?,
1437 );
1438 let mut previous = 0_u64;
1439 for (at, &ordinal) in summary.ordinals.iter().enumerate() {
1440 let delta = if at == 0 {
1441 ordinal
1442 } else {
1443 ordinal
1444 .checked_sub(previous)
1445 .ok_or_else(|| invalid("frequency ordinals are not ordered"))?
1446 };
1447 if at != 0 && delta == 0 {
1448 return Err(invalid("frequency ordinals are not unique"));
1449 }
1450 put_var_u64(&mut out, delta);
1451 previous = ordinal;
1452 }
1453 }
1454 Ok(out)
1455}
1456
1457struct Cursor<'a> {
1458 bytes: &'a [u8],
1459 at: usize,
1460}
1461impl<'a> Cursor<'a> {
1462 fn take(&mut self, len: usize) -> Result<&'a [u8]> {
1463 let end = self.at.checked_add(len).ok_or_else(|| invalid("directory offset overflow"))?;
1464 let bytes =
1465 self.bytes.get(self.at..end).ok_or_else(|| invalid("directory is truncated"))?;
1466 self.at = end;
1467 Ok(bytes)
1468 }
1469 fn u8(&mut self) -> Result<u8> {
1470 Ok(self.take(1)?[0])
1471 }
1472 fn u16(&mut self) -> Result<u16> {
1473 Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("two bytes")))
1474 }
1475 fn u32(&mut self) -> Result<u32> {
1476 Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("four bytes")))
1477 }
1478 fn u64(&mut self) -> Result<u64> {
1479 Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("eight bytes")))
1480 }
1481 fn var_u64(&mut self) -> Result<u64> {
1482 let mut value = 0_u64;
1483 for shift in (0..=63).step_by(7) {
1484 let byte = self.u8()?;
1485 let part = u64::from(byte & 0x7f);
1486 if shift == 63 && part > 1 {
1487 return Err(invalid("frequency ordinal varint overflows"));
1488 }
1489 value |= part << shift;
1490 if byte & 0x80 == 0 {
1491 return Ok(value);
1492 }
1493 }
1494 Err(invalid("frequency ordinal varint is too long"))
1495 }
1496 fn bound(&mut self) -> Result<Option<Bound>> {
1497 Ok(match self.u8()? {
1498 0 => None,
1499 1 => Some(Bound::Int(i128::from_le_bytes(
1500 self.take(16)?.try_into().expect("sixteen bytes"),
1501 ))),
1502 2 => Some(Bound::Real(f64::from_le_bytes(
1503 self.take(8)?.try_into().expect("eight bytes"),
1504 ))),
1505 3 => {
1506 let length = self.u32()? as usize;
1507 Some(Bound::Bytes(self.take(length)?.to_vec()))
1508 }
1509 _ => return Err(invalid("bound tag differs")),
1510 })
1511 }
1512 fn text(&mut self) -> Result<String> {
1513 let len = self.u16()? as usize;
1514 String::from_utf8(self.take(len)?.to_vec()).map_err(|_| invalid("name is not UTF-8"))
1515 }
1516}
1517
1518fn decode_directory(bytes: &[u8], size: u64, version: u32) -> Result<Table> {
1519 let mut cur = Cursor { bytes, at: 0 };
1520 let expected = if version == 7 { DIRECTORY_V7 } else { DIRECTORY };
1521 if cur.take(8)? != expected {
1522 return Err(invalid("directory magic differs"));
1523 }
1524 let name = cur.text()?;
1525 let width = cur.u16()? as usize;
1526 let mut fields = Vec::with_capacity(width);
1527 for _ in 0..width {
1528 let name = cur.text()?;
1529 let ty = tag_type(cur.u8()?)?;
1530 let not_null = match cur.u8()? {
1531 0 => false,
1532 1 => true,
1533 _ => return Err(invalid("nullability flag differs")),
1534 };
1535 fields.push(Field { name, ty, not_null });
1536 }
1537 let mut dictionaries = Vec::with_capacity(width);
1538 for _ in 0..width {
1539 dictionaries.push(match cur.u8()? {
1540 0 => None,
1541 1 => {
1542 let page = Page { offset: cur.u64()?, length: cur.u32()?, hash: cur.u64()? };
1543 let end = page
1544 .offset
1545 .checked_add(u64::from(page.length))
1546 .ok_or_else(|| invalid("dictionary page offset overflow"))?;
1547 if page.offset < HEADER || end > size {
1552 return Err(invalid("dictionary page range is outside the file"));
1553 }
1554 Some(page)
1555 }
1556 _ => return Err(invalid("dictionary page tag differs")),
1557 });
1558 }
1559 let rows = usize::try_from(cur.u64()?).map_err(|_| invalid("row count does not fit"))?;
1560 let count = cur.u32()? as usize;
1561 let mut stripes = Vec::with_capacity(count);
1562 let mut total = 0_usize;
1563 for _ in 0..count {
1564 let stripe_rows = cur.u32()? as usize;
1565 if stripe_rows == 0 {
1566 return Err(invalid("empty stripe"));
1567 }
1568 total =
1569 total.checked_add(stripe_rows).ok_or_else(|| invalid("stripe row count overflow"))?;
1570 let mut pages = Vec::with_capacity(width);
1571 for _ in 0..width {
1572 let offset = cur.u64()?;
1573 let length = cur.u32()?;
1574 let hash = cur.u64()?;
1575 let end = offset
1576 .checked_add(u64::from(length))
1577 .ok_or_else(|| invalid("page offset overflow"))?;
1578 if offset < HEADER || end > size || length as usize > MAX_PAGE {
1579 return Err(invalid("page range is outside the file"));
1580 }
1581 pages.push(Page { offset, length, hash });
1582 }
1583 let mut memberships = vec![None; width];
1584 if version >= 8 {
1585 for (column, field) in fields.iter().enumerate() {
1586 if field.ty != LogicalType::Varchar {
1587 continue;
1588 }
1589 let page = Page { offset: cur.u64()?, length: cur.u32()?, hash: cur.u64()? };
1590 let end = page
1591 .offset
1592 .checked_add(u64::from(page.length))
1593 .ok_or_else(|| invalid("membership page offset overflow"))?;
1594 if page.offset < HEADER || end > size || page.length as usize > MAX_PAGE {
1595 return Err(invalid("membership page range is outside the file"));
1596 }
1597 memberships[column] = Some(page);
1598 }
1599 }
1600 let mut ranges = Vec::with_capacity(width);
1601 for _ in 0..width {
1602 let low = cur.bound()?;
1603 let high = cur.bound()?;
1604 let nulls = cur.u32()? as usize;
1605 if nulls > stripe_rows {
1606 return Err(invalid("null count exceeds stripe rows"));
1607 }
1608 ranges.push(Range { low, high, nulls });
1609 }
1610 stripes.push(Stripe {
1611 rows: stripe_rows,
1612 pages,
1613 memberships,
1614 zone: Zone::from_ranges(ranges),
1615 });
1616 }
1617 if total != rows {
1618 return Err(invalid("table row count differs from stripes"));
1619 }
1620 let frequencies = if cur.at == bytes.len() {
1621 vec![None; width]
1622 } else {
1623 let frequency_version = match cur.take(8)? {
1624 magic if magic == FREQUENCIES_V1 => 1,
1625 magic if magic == FREQUENCIES => 2,
1626 _ => return Err(invalid("directory extension magic differs")),
1627 };
1628 if cur.u16()? as usize != width {
1629 return Err(invalid("frequency column count differs"));
1630 }
1631 let mut frequencies = Vec::with_capacity(width);
1632 for field in &fields {
1633 let summary = match cur.u8()? {
1634 0 => None,
1635 1 => {
1636 let omitted_max = cur.u64()?;
1637 let count = cur.u32()? as usize;
1638 if count > FREQUENCY_ENTRIES {
1639 return Err(invalid("frequency entry count exceeds its bound"));
1640 }
1641 let mut entries = Vec::with_capacity(count);
1642 for _ in 0..count {
1644 let value = match cur.u8()? {
1645 0 => FrequencyValue::Null,
1646 1 => FrequencyValue::Integer(i128::from_le_bytes(
1647 cur.take(16)?.try_into().expect("sixteen bytes"),
1648 )),
1649 2 => FrequencyValue::Code(cur.u32()?),
1650 _ => return Err(invalid("frequency value tag differs")),
1651 };
1652 let valid = matches!(
1653 (&field.ty, value),
1654 (_, FrequencyValue::Null)
1655 | (LogicalType::Varchar, FrequencyValue::Code(_))
1656 | (
1657 LogicalType::TinyInt
1658 | LogicalType::SmallInt
1659 | LogicalType::Integer
1660 | LogicalType::BigInt
1661 | LogicalType::UTinyInt
1662 | LogicalType::USmallInt
1663 | LogicalType::UInteger
1664 | LogicalType::UBigInt
1665 | LogicalType::Date
1666 | LogicalType::Timestamp,
1667 FrequencyValue::Integer(_),
1668 )
1669 );
1670 if !valid {
1671 return Err(invalid("frequency value does not match its column"));
1672 }
1673 let count = cur.u64()?;
1674 if count == 0 || count > rows as u64 {
1675 return Err(invalid("frequency count is outside the table"));
1676 }
1677 entries.push(FrequencyEntry { value, count });
1678 }
1679 if entries.windows(2).any(|pair| pair[0].count < pair[1].count) {
1680 return Err(invalid("frequency entries are not descending"));
1681 }
1682 let ordinals = if frequency_version == 1 {
1683 Vec::new()
1684 } else {
1685 let ordinal_count = cur.u32()? as usize;
1686 if ordinal_count > FREQUENCY_ORDINALS || ordinal_count > rows {
1687 return Err(invalid("frequency ordinal count exceeds its bound"));
1688 }
1689 let mut ordinals = Vec::with_capacity(ordinal_count);
1690 let mut previous = 0_u64;
1691 for at in 0..ordinal_count {
1692 let delta = cur.var_u64()?;
1693 if at != 0 && delta == 0 {
1694 return Err(invalid("frequency ordinals are not increasing"));
1695 }
1696 let ordinal = if at == 0 {
1697 delta
1698 } else {
1699 previous
1700 .checked_add(delta)
1701 .ok_or_else(|| invalid("frequency ordinal overflows"))?
1702 };
1703 if ordinal >= rows as u64 {
1704 return Err(invalid("frequency ordinal is outside the table"));
1705 }
1706 ordinals.push(ordinal);
1707 previous = ordinal;
1708 }
1709 ordinals
1710 };
1711 Some(FrequencySummary { entries, omitted_max, ordinals })
1712 }
1713 _ => return Err(invalid("frequency summary tag differs")),
1714 };
1715 frequencies.push(summary);
1716 }
1717 frequencies
1718 };
1719 if cur.at != bytes.len() {
1720 return Err(invalid("directory has trailing bytes"));
1721 }
1722 Ok(Table { name, fields, stripes, rows, dictionaries, frequencies })
1723}
1724
1725fn put_bound(out: &mut Vec<u8>, bound: Option<&Bound>) -> Result<()> {
1726 match bound {
1727 None => out.push(0),
1728 Some(Bound::Int(value)) => {
1729 out.push(1);
1730 out.extend_from_slice(&value.to_le_bytes());
1731 }
1732 Some(Bound::Real(value)) => {
1733 out.push(2);
1734 out.extend_from_slice(&value.to_le_bytes());
1735 }
1736 Some(Bound::Bytes(value)) => {
1737 out.push(3);
1738 put_u32(out, u32::try_from(value.len()).map_err(|_| invalid("bound length overflow"))?);
1739 out.extend_from_slice(value);
1740 }
1741 }
1742 Ok(())
1743}
1744
1745fn encode(
1746 vector: &Vector,
1747 global: Option<&mut GlobalDictionary>,
1748) -> Result<(Vec<u8>, Option<Vec<u8>>)> {
1749 let ty = vector.logical_type();
1750 let flat = vector.flatten()?;
1752 let mut out = Vec::new();
1753 let mut global_codes = None;
1754 if let Some(global) = global {
1755 let mut codes = Vec::with_capacity(flat.len());
1756 for row in 0..flat.len() {
1757 let text = flat.text_at(row).unwrap_or("");
1758 let code = global.code(text)?;
1759 global.observe(code, flat.is_null_at(row))?;
1760 codes.push(code);
1761 }
1762 global_codes = Some(codes);
1763 }
1764 let membership = global_codes.as_deref().map(encode_membership);
1765 let dictionary = if global_codes.is_none() && ty == &LogicalType::Varchar {
1766 string_dictionary(&flat)?
1767 } else {
1768 None
1769 };
1770 let packed_vector = if dictionary.is_none() && global_codes.is_none() {
1771 Some(flat.bit_packed()?)
1772 } else {
1773 None
1774 };
1775 let packed = packed_vector.as_ref().and_then(Vector::packed_parts);
1776 out.push(if global_codes.is_some() {
1777 3
1778 } else if dictionary.is_some() {
1779 1
1780 } else if packed.is_some() {
1781 2
1782 } else {
1783 0
1784 });
1785 let nulls = flat.validity();
1786 let flag = match nulls {
1787 Validity::AllValid => 0,
1788 Validity::AllInvalid => 1,
1789 Validity::Mask(_) => 2,
1790 };
1791 out.push(flag);
1792 if flag == 2 {
1793 for group in (0..vector.len()).step_by(8) {
1794 let mut bits = 0_u8;
1795 for bit in 0..8 {
1796 if group + bit < vector.len() && !flat.is_null_at(group + bit) {
1797 bits |= 1 << bit;
1798 }
1799 }
1800 out.push(bits);
1801 }
1802 }
1803 if let Some(codes) = global_codes {
1804 for code in codes {
1805 put_u32(&mut out, code);
1806 }
1807 return Ok((out, membership));
1808 }
1809 if let Some(dictionary) = dictionary {
1810 out.extend_from_slice(&dictionary);
1811 return Ok((out, membership));
1812 }
1813 if let Some(packed) = packed {
1814 if packed.offset() != 0 {
1815 return Err(invalid("writer received a sliced packed vector"));
1816 }
1817 out.push(u8::try_from(packed.width()).map_err(|_| invalid("packed width overflow"))?);
1818 out.extend_from_slice(&packed.base().to_le_bytes());
1819 put_u32(
1820 &mut out,
1821 u32::try_from(packed.words().len()).map_err(|_| invalid("too many packed words"))?,
1822 );
1823 for word in packed.words() {
1824 put_u64(&mut out, *word);
1825 }
1826 return Ok((out, membership));
1827 }
1828 let data = flat.data().ok_or_else(|| invalid("scalar column did not flatten"))?;
1829 match (ty, data) {
1830 (LogicalType::TinyInt, Data::Int8(values)) => {
1831 for value in &**values {
1832 out.extend_from_slice(&value.to_le_bytes());
1833 }
1834 }
1835 (LogicalType::UTinyInt, Data::UInt8(values)) => {
1836 for value in &**values {
1837 out.extend_from_slice(&value.to_le_bytes());
1838 }
1839 }
1840 (LogicalType::SmallInt, Data::Int16(values)) => {
1841 for value in &**values {
1842 out.extend_from_slice(&value.to_le_bytes());
1843 }
1844 }
1845 (LogicalType::USmallInt, Data::UInt16(values)) => {
1846 for value in &**values {
1847 out.extend_from_slice(&value.to_le_bytes());
1848 }
1849 }
1850 (LogicalType::UInteger, Data::UInt32(values)) => {
1851 for value in &**values {
1852 out.extend_from_slice(&value.to_le_bytes());
1853 }
1854 }
1855 (LogicalType::UBigInt, Data::UInt64(values)) => {
1856 for value in &**values {
1857 out.extend_from_slice(&value.to_le_bytes());
1858 }
1859 }
1860 (LogicalType::Integer | LogicalType::Date, Data::Int32(values)) => {
1861 for value in &**values {
1862 out.extend_from_slice(&value.to_le_bytes());
1863 }
1864 }
1865 (LogicalType::BigInt | LogicalType::Timestamp, Data::Int64(values)) => {
1866 for value in &**values {
1867 out.extend_from_slice(&value.to_le_bytes());
1868 }
1869 }
1870 (LogicalType::Boolean, Data::Bool(values)) => {
1871 for value in &**values {
1872 out.push(u8::from(*value));
1873 }
1874 }
1875 (LogicalType::Varchar, Data::Varlen(values)) => {
1876 let mut bytes = Vec::new();
1877 put_u32(&mut out, 0);
1878 for row in 0..vector.len() {
1879 let value = values.bytes(row).ok_or_else(|| invalid("string view is invalid"))?;
1880 bytes.extend_from_slice(value);
1881 put_u32(
1882 &mut out,
1883 u32::try_from(bytes.len())
1884 .map_err(|_| invalid("string payload exceeds 4GiB"))?,
1885 );
1886 }
1887 out.extend_from_slice(&bytes);
1888 }
1889 _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
1890 }
1891 Ok((out, membership))
1892}
1893
1894fn put_varint(out: &mut Vec<u8>, mut value: u32) {
1895 while value >= 0x80 {
1896 out.push((value as u8 & 0x7f) | 0x80);
1897 value >>= 7;
1898 }
1899 out.push(value as u8);
1900}
1901
1902fn encode_membership(codes: &[u32]) -> Vec<u8> {
1903 let mut unique = codes.to_vec();
1904 unique.sort_unstable();
1905 unique.dedup();
1906 let mut out = Vec::with_capacity(unique.len().saturating_mul(2).saturating_add(5));
1907 put_varint(&mut out, u32::try_from(unique.len()).unwrap_or(u32::MAX));
1908 let mut previous = 0;
1909 for (at, code) in unique.into_iter().enumerate() {
1910 put_varint(&mut out, if at == 0 { code } else { code - previous });
1911 previous = code;
1912 }
1913 out
1914}
1915
1916fn take_varint(bytes: &[u8], at: &mut usize) -> Result<u32> {
1917 let mut value = 0_u32;
1918 for shift in (0..35).step_by(7) {
1919 let byte = *bytes.get(*at).ok_or_else(|| invalid("membership varint is truncated"))?;
1920 *at += 1;
1921 let part = u32::from(byte & 0x7f);
1922 if shift == 28 && part > 0x0f {
1923 return Err(invalid("membership varint overflow"));
1924 }
1925 value = value
1926 .checked_add(
1927 part.checked_shl(shift).ok_or_else(|| invalid("membership varint overflow"))?,
1928 )
1929 .ok_or_else(|| invalid("membership varint overflow"))?;
1930 if byte & 0x80 == 0 {
1931 return Ok(value);
1932 }
1933 }
1934 Err(invalid("membership varint is too long"))
1935}
1936
1937fn decode_membership(bytes: &[u8]) -> Result<Vec<u32>> {
1938 let mut at = 0;
1939 let count = take_varint(bytes, &mut at)? as usize;
1940 let mut codes = Vec::with_capacity(count);
1941 let mut previous = 0_u32;
1942 for index in 0..count {
1943 let delta = take_varint(bytes, &mut at)?;
1944 let code = if index == 0 {
1945 delta
1946 } else {
1947 previous.checked_add(delta).ok_or_else(|| invalid("membership code overflow"))?
1948 };
1949 if index > 0 && code <= previous {
1950 return Err(invalid("membership codes are not increasing"));
1951 }
1952 codes.push(code);
1953 previous = code;
1954 }
1955 if at != bytes.len() {
1956 return Err(invalid("membership page has trailing bytes"));
1957 }
1958 Ok(codes)
1959}
1960
1961fn string_dictionary(vector: &Vector) -> Result<Option<Vec<u8>>> {
1962 let mut by_text = HashMap::new();
1963 let mut values = Vec::new();
1964 let mut codes = Vec::with_capacity(vector.len());
1965 let mut plain_bytes = 0_usize;
1966 for row in 0..vector.len() {
1967 let text = vector.text_at(row).unwrap_or("");
1968 plain_bytes = plain_bytes.saturating_add(text.len());
1969 let code = match by_text.get(text) {
1970 Some(&code) => code,
1971 None => {
1972 let code = u32::try_from(values.len())
1973 .map_err(|_| invalid("too many dictionary values"))?;
1974 by_text.insert(text, code);
1975 values.push(text);
1976 code
1977 }
1978 };
1979 codes.push(code);
1980 }
1981 let dictionary_bytes = values.iter().map(|value| value.len()).sum::<usize>();
1982 let encoded = 8_usize
1983 .saturating_add((values.len() + 1).saturating_mul(4))
1984 .saturating_add(dictionary_bytes)
1985 .saturating_add(codes.len().saturating_mul(4));
1986 let plain = (vector.len() + 1).saturating_mul(4).saturating_add(plain_bytes);
1987 if encoded >= plain {
1988 return Ok(None);
1989 }
1990 let mut out = Vec::with_capacity(encoded);
1991 put_u32(
1992 &mut out,
1993 u32::try_from(values.len()).map_err(|_| invalid("too many dictionary values"))?,
1994 );
1995 put_u32(
1996 &mut out,
1997 u32::try_from(dictionary_bytes).map_err(|_| invalid("dictionary payload exceeds 4GiB"))?,
1998 );
1999 let mut offset = 0_u32;
2000 put_u32(&mut out, offset);
2001 for value in &values {
2002 offset = offset
2003 .checked_add(
2004 u32::try_from(value.len()).map_err(|_| invalid("dictionary value is too long"))?,
2005 )
2006 .ok_or_else(|| invalid("dictionary payload exceeds 4GiB"))?;
2007 put_u32(&mut out, offset);
2008 }
2009 for value in values {
2010 out.extend_from_slice(value.as_bytes());
2011 }
2012 for code in codes {
2013 put_u32(&mut out, code);
2014 }
2015 Ok(Some(out))
2016}
2017
2018struct EncodedDictionary {
2019 index: Vec<u8>,
2020 payload: Vec<u8>,
2021}
2022
2023fn encode_global_dictionary(dictionary: GlobalDictionary) -> Result<EncodedDictionary> {
2024 let values = dictionary.offsets.len() - 1;
2025 let payload_len = dictionary.payload.len();
2026 let blocks = payload_len.div_ceil(TEXT_PAYLOAD_BLOCK);
2027 let mut index = Vec::with_capacity(12 + (values + 1) * 4 + blocks * 8);
2028 put_u32(
2029 &mut index,
2030 u32::try_from(values).map_err(|_| invalid("global dictionary has too many values"))?,
2031 );
2032 put_u32(&mut index, TEXT_PAYLOAD_BLOCK as u32);
2033 put_u32(
2034 &mut index,
2035 u32::try_from(blocks).map_err(|_| invalid("global dictionary has too many blocks"))?,
2036 );
2037 for offset in dictionary.offsets {
2038 put_u32(&mut index, offset);
2039 }
2040 for block in dictionary.payload.chunks(TEXT_PAYLOAD_BLOCK) {
2041 put_u64(&mut index, checksum(block));
2042 }
2043 Ok(EncodedDictionary { index, payload: dictionary.payload })
2044}
2045
2046fn open_global_dictionary(file: Arc<File>, page: Page, ty: &LogicalType) -> Result<Vector> {
2047 if ty != &LogicalType::Varchar {
2048 return Err(invalid("global dictionary belongs to a non-string column"));
2049 }
2050 let mut header = [0; 12];
2051 read_at(&file, page.offset, &mut header)?;
2052 let count = u32::from_le_bytes(header[0..4].try_into().expect("four bytes")) as usize;
2053 let block_size = u32::from_le_bytes(header[4..8].try_into().expect("four bytes")) as usize;
2054 let blocks = u32::from_le_bytes(header[8..12].try_into().expect("four bytes")) as usize;
2055 if block_size != TEXT_PAYLOAD_BLOCK {
2056 return Err(invalid("global dictionary block width differs"));
2057 }
2058 let offset_len = (count + 1)
2059 .checked_mul(4)
2060 .ok_or_else(|| invalid("global dictionary offset count overflow"))?;
2061 let hash_len =
2062 blocks.checked_mul(8).ok_or_else(|| invalid("global dictionary block count overflow"))?;
2063 let index_len = 12usize
2064 .checked_add(offset_len)
2065 .and_then(|len| len.checked_add(hash_len))
2066 .ok_or_else(|| invalid("global dictionary header overflow"))?;
2067 if index_len > page.length as usize {
2068 return Err(invalid("global dictionary offset index exceeds its page"));
2069 }
2070 let mut index = vec![0; index_len];
2071 index[..12].copy_from_slice(&header);
2072 read_at(&file, page.offset + 12, &mut index[12..])?;
2073 if checksum(&index) != page.hash {
2074 return Err(invalid("global dictionary index checksum differs"));
2075 }
2076 let offsets = index[12..12 + offset_len]
2077 .chunks_exact(4)
2078 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2079 .collect::<Vec<_>>();
2080 let hashes = index[12 + offset_len..]
2081 .chunks_exact(8)
2082 .map(|part| u64::from_le_bytes(part.try_into().expect("eight bytes")))
2083 .collect::<Vec<_>>();
2084 let payload_len = page.length as usize - index_len;
2085 if blocks != payload_len.div_ceil(TEXT_PAYLOAD_BLOCK) {
2086 return Err(invalid("global dictionary block count differs from its payload"));
2087 }
2088 if offsets.first() != Some(&0)
2089 || offsets.last().copied().map(|last| last as usize) != Some(payload_len)
2090 || offsets.windows(2).any(|pair| pair[0] > pair[1])
2091 {
2092 return Err(invalid("global dictionary offsets do not bound the payload"));
2093 }
2094 let payload_blocks =
2095 (0..payload_len.div_ceil(TEXT_PAYLOAD_BLOCK)).map(|_| OnceLock::new()).collect();
2096 let crossing = (0..count.div_ceil(TEXT_CROSSING_BLOCK)).map(|_| OnceLock::new()).collect();
2097 Vector::external_text(
2098 LogicalType::Varchar,
2099 Arc::new(NativeText {
2100 file,
2101 offsets,
2102 payload: page.offset + index_len as u64,
2103 payload_len,
2104 hashes,
2105 payload_blocks,
2106 crossing,
2107 }),
2108 )
2109}
2110
2111fn decode(
2112 ty: &LogicalType,
2113 rows: usize,
2114 bytes: &[u8],
2115 global: Option<Arc<Vector>>,
2116) -> Result<Vector> {
2117 let mut cur = Cursor { bytes, at: 0 };
2118 let codec = cur.u8()?;
2119 let flag = cur.u8()?;
2120 let validity = match flag {
2121 0 => Validity::AllValid,
2122 1 => Validity::AllInvalid,
2123 2 => {
2124 let mask = cur.take(rows.div_ceil(8))?;
2125 Validity::from_iter(rows, |row| mask[row / 8] >> (row % 8) & 1 == 1)
2126 }
2127 _ => return Err(invalid("page validity tag differs")),
2128 };
2129 if codec == 1 {
2130 if ty != &LogicalType::Varchar {
2131 return Err(invalid("dictionary codec belongs to a non-string page"));
2132 }
2133 let count = cur.u32()? as usize;
2134 let payload_len = cur.u32()? as usize;
2135 let offset_bytes = cur.take(
2136 (count + 1)
2137 .checked_mul(4)
2138 .ok_or_else(|| invalid("dictionary offset count overflow"))?,
2139 )?;
2140 let offsets = offset_bytes
2141 .chunks_exact(4)
2142 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2143 .collect::<Vec<_>>();
2144 let payload = cur.take(payload_len)?.to_vec();
2145 if offsets.first() != Some(&0)
2146 || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
2147 || offsets.windows(2).any(|pair| pair[0] > pair[1])
2148 {
2149 return Err(invalid("dictionary offsets do not bound the payload"));
2150 }
2151 let mut strings = StringColumn::over(Buffer::from_vec(payload));
2152 for pair in offsets.windows(2) {
2153 strings.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
2154 }
2155 let mut codes = Vec::with_capacity(rows);
2156 for _ in 0..rows {
2157 codes.push(cur.u32()?);
2158 }
2159 if codes.iter().any(|code| *code as usize >= count) {
2160 return Err(invalid("dictionary code is out of range"));
2161 }
2162 if cur.at != bytes.len() {
2163 return Err(invalid("dictionary page has trailing bytes"));
2164 }
2165 let dictionary = Vector::flat(LogicalType::Varchar, Data::Varlen(strings))?;
2166 return Ok(Vector::dictionary(codes, dictionary)?.with_validity(validity));
2167 }
2168 if codec == 3 {
2169 let dictionary = global.ok_or_else(|| invalid("global code page has no dictionary"))?;
2170 let mut codes = Vec::with_capacity(rows);
2171 let mut highest = None;
2172 for _ in 0..rows {
2173 let code = cur.u32()?;
2174 highest = Some(highest.map_or(code, |old: u32| old.max(code)));
2175 codes.push(code);
2176 }
2177 if cur.at != bytes.len() {
2178 return Err(invalid("global code page has trailing bytes"));
2179 }
2180 return Ok(Vector::stable_dictionary_validated(codes, dictionary, highest)?
2181 .with_validity(validity));
2182 }
2183 if codec == 2 {
2184 let width = u32::from(cur.u8()?);
2185 let base = i128::from_le_bytes(cur.take(16)?.try_into().expect("sixteen bytes"));
2186 let count = cur.u32()? as usize;
2187 let mut words = Vec::with_capacity(count);
2188 for _ in 0..count {
2189 words.push(cur.u64()?);
2190 }
2191 if cur.at != bytes.len() {
2192 return Err(invalid("packed page has trailing bytes"));
2193 }
2194 return Ok(Vector::packed(ty.clone(), words, width, base, rows)?.with_validity(validity));
2195 }
2196 if codec != 0 {
2197 return Err(invalid("page codec is unknown"));
2198 }
2199 let data = match ty {
2200 LogicalType::TinyInt => {
2201 let values = cur.take(rows)?;
2202 Data::Int8(values.iter().map(|item| *item as i8).collect::<Vec<_>>().into())
2203 }
2204 LogicalType::UTinyInt => Data::UInt8(cur.take(rows)?.to_vec().into()),
2205 LogicalType::SmallInt => {
2206 let values =
2207 cur.take(rows.checked_mul(2).ok_or_else(|| invalid("page size overflow"))?)?;
2208 Data::Int16(
2209 values
2210 .chunks_exact(2)
2211 .map(|item| i16::from_le_bytes(item.try_into().expect("two bytes")))
2212 .collect::<Vec<_>>()
2213 .into(),
2214 )
2215 }
2216 LogicalType::USmallInt => {
2217 let values =
2218 cur.take(rows.checked_mul(2).ok_or_else(|| invalid("page size overflow"))?)?;
2219 Data::UInt16(
2220 values
2221 .chunks_exact(2)
2222 .map(|item| u16::from_le_bytes(item.try_into().expect("two bytes")))
2223 .collect::<Vec<_>>()
2224 .into(),
2225 )
2226 }
2227 LogicalType::UInteger => {
2228 let values =
2229 cur.take(rows.checked_mul(4).ok_or_else(|| invalid("page size overflow"))?)?;
2230 Data::UInt32(
2231 values
2232 .chunks_exact(4)
2233 .map(|item| u32::from_le_bytes(item.try_into().expect("four bytes")))
2234 .collect::<Vec<_>>()
2235 .into(),
2236 )
2237 }
2238 LogicalType::UBigInt => {
2239 let values =
2240 cur.take(rows.checked_mul(8).ok_or_else(|| invalid("page size overflow"))?)?;
2241 Data::UInt64(
2242 values
2243 .chunks_exact(8)
2244 .map(|item| u64::from_le_bytes(item.try_into().expect("eight bytes")))
2245 .collect::<Vec<_>>()
2246 .into(),
2247 )
2248 }
2249 LogicalType::Integer | LogicalType::Date => {
2250 let values =
2251 cur.take(rows.checked_mul(4).ok_or_else(|| invalid("page size overflow"))?)?;
2252 Data::Int32(
2253 values
2254 .chunks_exact(4)
2255 .map(|item| i32::from_le_bytes(item.try_into().expect("four bytes")))
2256 .collect::<Vec<_>>()
2257 .into(),
2258 )
2259 }
2260 LogicalType::BigInt | LogicalType::Timestamp => {
2261 let values =
2262 cur.take(rows.checked_mul(8).ok_or_else(|| invalid("page size overflow"))?)?;
2263 Data::Int64(
2264 values
2265 .chunks_exact(8)
2266 .map(|item| i64::from_le_bytes(item.try_into().expect("eight bytes")))
2267 .collect::<Vec<_>>()
2268 .into(),
2269 )
2270 }
2271 LogicalType::Boolean => {
2272 let values = cur.take(rows)?;
2273 if values.iter().any(|value| *value > 1) {
2274 return Err(invalid("boolean page has another value"));
2275 }
2276 Data::Bool(values.iter().map(|value| *value == 1).collect::<Vec<_>>().into())
2277 }
2278 LogicalType::Varchar => {
2279 let offset_bytes = cur
2280 .take((rows + 1).checked_mul(4).ok_or_else(|| invalid("offset count overflow"))?)?;
2281 let offsets = offset_bytes
2282 .chunks_exact(4)
2283 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2284 .collect::<Vec<_>>();
2285 let payload = cur.take(bytes.len() - cur.at)?.to_vec();
2286 if offsets.first() != Some(&0)
2287 || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
2288 || offsets.windows(2).any(|pair| pair[0] > pair[1])
2289 {
2290 return Err(invalid("string offsets do not bound the payload"));
2291 }
2292 let mut values = StringColumn::over(Buffer::from_vec(payload));
2293 for pair in offsets.windows(2) {
2294 values.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
2295 }
2296 Data::Varlen(values)
2297 }
2298 _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
2299 };
2300 if cur.at != bytes.len() {
2301 return Err(invalid("page has trailing bytes"));
2302 }
2303 Ok(Vector::flat(ty.clone(), data)?.with_validity(validity))
2304}
2305
2306#[cfg(test)]
2307mod tests {
2308 use std::fs;
2309 use std::io::{Seek, SeekFrom, Write};
2310 use std::path::PathBuf;
2311 use std::time::{SystemTime, UNIX_EPOCH};
2312
2313 use rudb_common::Value;
2314 use rudb_common::bounds::Op;
2315
2316 use super::*;
2317
2318 #[test]
2319 fn checksum_matches_fixed_vectors() {
2320 assert_eq!(checksum(b""), 0xef46_db37_51d8_e999);
2321 assert_eq!(checksum(b"a"), 0xd24e_c4f1_a98c_6e5b);
2322 assert_eq!(checksum(b"abc"), 0x44bc_2cf5_ad77_0999);
2323 }
2324
2325 fn path(label: &str) -> PathBuf {
2326 let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
2327 std::env::temp_dir().join(format!("rudb-native-{label}-{}-{stamp}.rdb", std::process::id()))
2328 }
2329
2330 fn sample() -> Chunk {
2331 Chunk::new(vec![
2332 Vector::from_values(
2333 LogicalType::Integer,
2334 &[Value::Integer(4), Value::Integer(9), Value::Integer(-2)],
2335 )
2336 .expect("integers"),
2337 Vector::from_values(
2338 LogicalType::Varchar,
2339 &[
2340 Value::Varchar("alpha".into()),
2341 Value::Null,
2342 Value::Varchar("long text after a slash".into()),
2343 ],
2344 )
2345 .expect("strings"),
2346 ])
2347 .expect("matching rows")
2348 }
2349
2350 #[test]
2351 fn committed_file_reopens_and_reads_only_requested_columns() {
2352 let path = path("reopen");
2353 let mut writer = Writer::create(
2354 &path,
2355 "items",
2356 vec![
2357 Field::required("id", LogicalType::Integer),
2358 Field::new("text", LogicalType::Varchar),
2359 ],
2360 )
2361 .expect("new file");
2362 writer.append(&sample()).expect("first stripe");
2363 writer.append(&sample()).expect("second stripe");
2364 writer.finish().expect("commit");
2365 let reader = Reader::open(&path).expect("reopen from disk");
2366 assert_eq!(reader.table().rows(), 6);
2367 assert_eq!(reader.table().stripes().len(), 2);
2368 let text = reader.read(1, &[1]).expect("only text page");
2369 assert_eq!(text.width(), 1);
2370 assert_eq!(text.value_at(1, 0), Value::Null);
2371 assert_eq!(text.value_at(2, 0), Value::Varchar("long text after a slash".into()));
2372 let sparse = reader.read_sparse(1, &[1]).expect("one page without extent prefetch");
2373 assert_eq!(sparse.width(), 1);
2374 assert_eq!(sparse.value_at(1, 0), Value::Null);
2375 assert_eq!(sparse.value_at(2, 0), Value::Varchar("long text after a slash".into()));
2376 assert!(!reader.skips_codes(0, 1, &[0]).expect("alpha is in the stripe"));
2377 assert!(!reader.skips_codes(0, 1, &[2]).expect("long text is in the stripe"));
2378 assert!(reader.skips_codes(0, 1, &[3]).expect("unknown code is absent"));
2379 let count = reader.read(0, &[]).expect("no page is needed for count");
2380 assert_eq!(count.len(), 3);
2381 assert!(reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(100) }]));
2382 assert!(!reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(0) }]));
2383 let integers = reader.top_frequencies(0, 1).expect("valid integer synopsis").expect("kept");
2384 assert_eq!(
2385 integers,
2386 vec![(Value::Integer(-2), 2), (Value::Integer(4), 2), (Value::Integer(9), 2),]
2387 );
2388 let strings = reader.top_frequencies(1, 1).expect("valid string synopsis").expect("kept");
2389 assert_eq!(strings.len(), 3);
2390 assert!(strings.contains(&(Value::Null, 2)));
2391 assert!(strings.contains(&(Value::Varchar("alpha".into()), 2)));
2392 assert!(strings.contains(&(Value::Varchar("long text after a slash".into()), 2)));
2393 fs::remove_file(path).expect("remove scratch file");
2394 }
2395
2396 #[test]
2403 fn every_integer_width_round_trips_through_a_page() {
2404 let path = path("integer-widths");
2405 let columns = [
2406 (LogicalType::TinyInt, vec![Value::TinyInt(i8::MIN), Value::TinyInt(i8::MAX)]),
2407 (LogicalType::UTinyInt, vec![Value::UTinyInt(0), Value::UTinyInt(u8::MAX)]),
2408 (LogicalType::SmallInt, vec![Value::SmallInt(i16::MIN), Value::SmallInt(i16::MAX)]),
2409 (LogicalType::USmallInt, vec![Value::USmallInt(0), Value::USmallInt(u16::MAX)]),
2410 (LogicalType::Integer, vec![Value::Integer(i32::MIN), Value::Integer(i32::MAX)]),
2411 (LogicalType::UInteger, vec![Value::UInteger(0), Value::UInteger(u32::MAX)]),
2412 (LogicalType::BigInt, vec![Value::BigInt(i64::MIN), Value::BigInt(i64::MAX)]),
2413 (LogicalType::UBigInt, vec![Value::UBigInt(0), Value::UBigInt(u64::MAX)]),
2414 ];
2415 let fields = columns
2416 .iter()
2417 .enumerate()
2418 .map(|(at, (ty, _))| Field::required(format!("c{at}"), ty.clone()))
2419 .collect::<Vec<_>>();
2420 let vectors = columns
2421 .iter()
2422 .map(|(ty, values)| Vector::from_values(ty.clone(), values).expect("a vector"))
2423 .collect::<Vec<_>>();
2424 let mut writer = Writer::create(&path, "widths", fields).expect("new file");
2425 writer.append(&Chunk::new(vectors).expect("matching rows")).expect("one stripe");
2426 writer.finish().expect("commit");
2427
2428 let reader = Reader::open(&path).expect("reopen from disk");
2429 let wanted = (0..columns.len()).collect::<Vec<_>>();
2430 let read = reader.read(0, &wanted).expect("every column");
2431 assert_eq!(read.len(), 2);
2432 for (at, (ty, values)) in columns.iter().enumerate() {
2434 assert_eq!(read.value_at(0, at), values[0], "the low end of {ty}");
2435 assert_eq!(read.value_at(1, at), values[1], "the high end of {ty}");
2436 }
2437 fs::remove_file(path).expect("remove scratch file");
2438 }
2439
2440 #[test]
2441 fn numeric_frequency_candidates_keep_bounded_row_ordinals() {
2442 let path = path("frequency-ordinals");
2443 let mut writer =
2444 Writer::create(&path, "items", vec![Field::required("id", LogicalType::BigInt)])
2445 .expect("new file");
2446 let mut values = Vec::new();
2447 for leader in 0..10_i64 {
2448 values.extend(std::iter::repeat_n(leader, 100));
2449 }
2450 values.extend(1_000_i64..41_000);
2451 for part in values.chunks(1_024) {
2452 let vector = Vector::flat(LogicalType::BigInt, Data::Int64(part.to_vec().into()))
2453 .expect("big integers");
2454 writer.append(&Chunk::new(vec![vector]).expect("one column")).expect("one stripe");
2455 }
2456 writer.finish().expect("commit");
2457
2458 let reader = Reader::open(&path).expect("reopen from disk");
2459 let occurrences =
2460 reader.frequency_occurrences(0).expect("valid metadata").expect("bounded ordinals");
2461 assert!(occurrences.omitted_max < 100);
2462 assert!(occurrences.ordinals.len() <= FREQUENCY_ORDINALS);
2463 assert!(occurrences.ordinals.windows(2).all(|pair| pair[0] < pair[1]));
2464 assert_eq!(&occurrences.ordinals[..1_000], &(0_u64..1_000).collect::<Vec<_>>());
2465 fs::remove_file(path).expect("remove scratch file");
2466 }
2467
2468 #[test]
2469 fn an_unfinished_or_damaged_file_does_not_answer_with_partial_rows() {
2470 let unfinished = path("unfinished");
2471 let mut writer =
2472 Writer::create(&unfinished, "items", vec![Field::new("id", LogicalType::Integer)])
2473 .expect("new file");
2474 let chunk = Chunk::new(vec![
2475 Vector::flat(LogicalType::Integer, Data::Int32(vec![1, 2, 3].into()))
2476 .expect("integers"),
2477 ])
2478 .expect("chunk");
2479 writer.append(&chunk).expect("page written");
2480 drop(writer);
2481 assert!(Reader::open(&unfinished).is_err(), "no directory was committed");
2482 fs::remove_file(unfinished).expect("remove scratch file");
2483
2484 let damaged = path("damaged");
2485 let mut writer =
2486 Writer::create(&damaged, "items", vec![Field::new("id", LogicalType::Integer)])
2487 .expect("new file");
2488 writer.append(&chunk).expect("page written");
2489 writer.finish().expect("commit");
2490 let reader = Reader::open(&damaged).expect("valid directory");
2491 let mut file =
2492 OpenOptions::new().write(true).open(&damaged).expect("open for a damaged page");
2493 file.seek(SeekFrom::Start(HEADER + 1)).expect("inside first page");
2494 file.write_all(&[255]).expect("damage one byte");
2495 assert!(reader.read(0, &[0]).is_err(), "page checksum rejects corruption");
2496 fs::remove_file(damaged).expect("remove scratch file");
2497 }
2498
2499 #[test]
2500 fn damaged_lazy_dictionary_payload_is_an_error() {
2501 let path = path("damaged-dictionary");
2502 let mut writer = Writer::create(
2503 &path,
2504 "items",
2505 vec![
2506 Field::required("id", LogicalType::Integer),
2507 Field::new("text", LogicalType::Varchar),
2508 ],
2509 )
2510 .expect("new file");
2511 writer.append(&sample()).expect("stripe written");
2512 writer.finish().expect("commit");
2513
2514 let reader = Reader::open(&path).expect("valid directory");
2515 let dictionary = reader.table.dictionaries[1].expect("string dictionary page");
2516 let index_len = 12_u64 + 4 * 4 + 8;
2517 let mut file = OpenOptions::new().write(true).open(&path).expect("open dictionary page");
2518 file.seek(SeekFrom::Start(dictionary.offset + index_len))
2519 .expect("inside dictionary payload");
2520 file.write_all(&[255]).expect("damage dictionary payload");
2521
2522 let chunk = reader.read(0, &[1]).expect("code page and dictionary index remain valid");
2523 let error =
2524 chunk.validate_external().expect_err("payload corruption must reach the caller");
2525 assert!(error.message().contains("payload checksum differs"), "{error}");
2526 fs::remove_file(path).expect("remove scratch file");
2527 }
2528
2529 #[test]
2530 fn damaged_membership_cannot_skip_a_string_page() {
2531 let path = path("damaged-membership");
2532 let mut writer = Writer::create(
2533 &path,
2534 "items",
2535 vec![
2536 Field::required("id", LogicalType::Integer),
2537 Field::new("text", LogicalType::Varchar),
2538 ],
2539 )
2540 .expect("new file");
2541 writer.append(&sample()).expect("stripe written");
2542 writer.finish().expect("commit");
2543
2544 let reader = Reader::open(&path).expect("valid directory");
2545 let membership = reader.table.stripes[0].memberships[1].expect("string membership");
2546 let mut file = OpenOptions::new().write(true).open(&path).expect("open membership page");
2547 file.seek(SeekFrom::Start(membership.offset)).expect("membership start");
2548 file.write_all(&[255]).expect("damage membership");
2549 let error = reader.skips_codes(0, 1, &[3]).expect_err("corruption must not skip rows");
2550 assert!(error.message().contains("membership page checksum differs"), "{error}");
2551 fs::remove_file(path).expect("remove scratch file");
2552 }
2553
2554 #[test]
2555 fn membership_delta_stream_is_sorted_exact_and_bounded() {
2556 let encoded = encode_membership(&[900, 4, 4, 72, 9, u32::MAX]);
2557 assert_eq!(
2558 decode_membership(&encoded).expect("valid membership"),
2559 [4, 9, 72, 900, u32::MAX]
2560 );
2561 assert!(decode_membership(&[1, 0x80]).is_err(), "a truncated varint is invalid");
2562 assert!(
2563 decode_membership(&[1, 0xff, 0xff, 0xff, 0xff, 0x10]).is_err(),
2564 "a value past u32 is invalid"
2565 );
2566 }
2567
2568 #[test]
2569 fn a_global_dictionary_may_be_larger_than_one_column_page() {
2570 let dictionary = Page {
2571 offset: HEADER,
2572 length: u32::try_from(MAX_PAGE + 1).expect("the page bound fits on disk"),
2573 hash: 0,
2574 };
2575 let table = Table {
2576 name: "items".to_owned(),
2577 fields: vec![Field::new("text", LogicalType::Varchar)],
2578 stripes: Vec::new(),
2579 rows: 0,
2580 dictionaries: vec![Some(dictionary)],
2581 frequencies: vec![None],
2582 };
2583 let directory = encode_directory(&table).expect("directory");
2584 let file_size = dictionary.offset + u64::from(dictionary.length) + 1;
2585
2586 let decoded = decode_directory(&directory, file_size, 8).expect("large lazy dictionary");
2587 assert_eq!(decoded.dictionaries[0].expect("dictionary").length, dictionary.length);
2588 let legacy = encode_directory_version(&table, 7).expect("legacy directory");
2589 let decoded = decode_directory(&legacy, file_size, 7).expect("v7 remains readable");
2590 assert_eq!(decoded.dictionaries[0].expect("legacy dictionary").length, dictionary.length);
2591 }
2592}