1#![forbid(unsafe_code)]
8
9use std::collections::HashMap;
10use std::fs::{File, OpenOptions};
11use std::io::{Read, Seek, SeekFrom, Write};
12use std::mem::size_of;
13use std::path::Path;
14use std::sync::Mutex;
15use std::sync::{Arc, OnceLock};
16
17use rudb_common::bounds::Bound;
18use rudb_common::{Error, Field, LogicalType, Result};
19use rudb_storage::{Probe, Range, Zone};
20use rudb_vector::string::StringColumn;
21use rudb_vector::validity::Validity;
22use rudb_vector::{Buffer, Chunk, Data, TextSource, Vector};
23
24const MAGIC: &[u8; 8] = b"RUDBNV7\0";
25const DIRECTORY: &[u8; 8] = b"RUDBDIR7";
26const HEADER: u64 = 80;
27const SLOT_BYTES: usize = 28;
28const MAX_PAGE: usize = 256 * 1024 * 1024;
29const MAX_DIRECTORY: usize = 128 * 1024 * 1024;
30
31fn io(error: std::io::Error) -> Error {
32 Error::io(error.to_string())
33}
34
35fn invalid(message: &str) -> Error {
36 Error::invalid_input(format!("invalid rudb native file: {message}"))
37}
38
39fn checksum(bytes: &[u8]) -> u64 {
40 const P1: u64 = 11_400_714_785_074_694_791;
41 const P2: u64 = 14_029_467_366_897_019_727;
42 const P3: u64 = 1_609_587_929_392_839_161;
43 const P4: u64 = 9_650_029_242_287_828_579;
44 const P5: u64 = 2_870_177_450_012_600_261;
45 let round = |state: u64, word: u64| {
46 state.wrapping_add(word.wrapping_mul(P2)).rotate_left(31).wrapping_mul(P1)
47 };
48 let merge = |state: u64, lane: u64| (state ^ round(0, lane)).wrapping_mul(P1).wrapping_add(P4);
49 let word =
50 |at: usize| u64::from_le_bytes(bytes[at..at + 8].try_into().expect("eight checksum bytes"));
51
52 let mut at = 0;
53 let mut hash = if bytes.len() >= 32 {
54 let mut one = P1.wrapping_add(P2);
55 let mut two = P2;
56 let mut three = 0;
57 let mut four = 0_u64.wrapping_sub(P1);
58 while at + 32 <= bytes.len() {
59 one = round(one, word(at));
60 two = round(two, word(at + 8));
61 three = round(three, word(at + 16));
62 four = round(four, word(at + 24));
63 at += 32;
64 }
65 let combined = one
66 .rotate_left(1)
67 .wrapping_add(two.rotate_left(7))
68 .wrapping_add(three.rotate_left(12))
69 .wrapping_add(four.rotate_left(18));
70 merge(merge(merge(merge(combined, one), two), three), four)
71 } else {
72 P5
73 };
74 hash = hash.wrapping_add(bytes.len() as u64);
75 while at + 8 <= bytes.len() {
76 hash ^= round(0, word(at));
77 hash = hash.rotate_left(27).wrapping_mul(P1).wrapping_add(P4);
78 at += 8;
79 }
80 if at + 4 <= bytes.len() {
81 let tail = u32::from_le_bytes(bytes[at..at + 4].try_into().expect("four checksum bytes"));
82 hash ^= u64::from(tail).wrapping_mul(P1);
83 hash = hash.rotate_left(23).wrapping_mul(P2).wrapping_add(P3);
84 at += 4;
85 }
86 while at < bytes.len() {
87 hash ^= u64::from(bytes[at]).wrapping_mul(P5);
88 hash = hash.rotate_left(11).wrapping_mul(P1);
89 at += 1;
90 }
91 hash ^= hash >> 33;
92 hash = hash.wrapping_mul(P2);
93 hash ^= hash >> 29;
94 hash = hash.wrapping_mul(P3);
95 hash ^ (hash >> 32)
96}
97
98#[derive(Debug, Clone, Copy)]
99struct Slot {
100 offset: u64,
101 length: u32,
102 generation: u64,
103 hash: u64,
104}
105
106impl Slot {
107 fn bytes(self) -> [u8; SLOT_BYTES] {
108 let mut result = [0; SLOT_BYTES];
109 result[..8].copy_from_slice(&self.offset.to_le_bytes());
110 result[8..12].copy_from_slice(&self.length.to_le_bytes());
111 result[12..20].copy_from_slice(&self.generation.to_le_bytes());
112 result[20..28].copy_from_slice(&self.hash.to_le_bytes());
113 result
114 }
115
116 fn read(bytes: &[u8]) -> Self {
117 Self {
118 offset: u64::from_le_bytes(bytes[..8].try_into().expect("eight bytes")),
119 length: u32::from_le_bytes(bytes[8..12].try_into().expect("four bytes")),
120 generation: u64::from_le_bytes(bytes[12..20].try_into().expect("eight bytes")),
121 hash: u64::from_le_bytes(bytes[20..28].try_into().expect("eight bytes")),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
127struct Page {
128 offset: u64,
129 length: u32,
130 hash: u64,
131}
132
133#[derive(Debug, Clone)]
135pub struct Stripe {
136 rows: usize,
137 pages: Vec<Page>,
138 zone: Zone,
139}
140
141impl Stripe {
142 #[must_use]
144 pub fn rows(&self) -> usize {
145 self.rows
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct Table {
152 name: String,
153 fields: Vec<Field>,
154 stripes: Vec<Stripe>,
155 rows: usize,
156 dictionaries: Vec<Option<Page>>,
157}
158
159impl Table {
160 #[must_use]
162 pub fn name(&self) -> &str {
163 &self.name
164 }
165
166 #[must_use]
168 pub fn fields(&self) -> &[Field] {
169 &self.fields
170 }
171
172 #[must_use]
174 pub fn rows(&self) -> usize {
175 self.rows
176 }
177
178 #[must_use]
180 pub fn stripes(&self) -> &[Stripe] {
181 &self.stripes
182 }
183}
184
185#[derive(Debug)]
187struct GlobalDictionary {
188 primary: HashMap<u64, u32>,
189 collisions: HashMap<u64, Vec<u32>>,
190 offsets: Vec<u32>,
191 payload: Vec<u8>,
192}
193
194impl GlobalDictionary {
195 fn new() -> Self {
196 Self {
197 primary: HashMap::new(),
198 collisions: HashMap::new(),
199 offsets: vec![0],
200 payload: Vec::new(),
201 }
202 }
203
204 fn bytes(&self, code: u32) -> Option<&[u8]> {
205 let start = *self.offsets.get(code as usize)? as usize;
206 let end = *self.offsets.get(code as usize + 1)? as usize;
207 self.payload.get(start..end)
208 }
209
210 fn code(&mut self, text: &str) -> Result<u32> {
211 let hash = checksum(text.as_bytes());
212 if let Some(&code) = self.primary.get(&hash) {
213 if self.bytes(code) == Some(text.as_bytes()) {
214 return Ok(code);
215 }
216 if let Some(codes) = self.collisions.get(&hash) {
217 if let Some(code) =
218 codes.iter().copied().find(|&code| self.bytes(code) == Some(text.as_bytes()))
219 {
220 return Ok(code);
221 }
222 }
223 let code = self.insert(text)?;
224 self.collisions.entry(hash).or_default().push(code);
225 return Ok(code);
226 }
227 let code = self.insert(text)?;
228 self.primary.insert(hash, code);
229 Ok(code)
230 }
231
232 fn insert(&mut self, text: &str) -> Result<u32> {
233 let code = u32::try_from(self.offsets.len() - 1)
234 .map_err(|_| invalid("global dictionary has too many values"))?;
235 self.payload.extend_from_slice(text.as_bytes());
236 self.offsets.push(
237 u32::try_from(self.payload.len())
238 .map_err(|_| invalid("global dictionary payload exceeds 4 GiB"))?,
239 );
240 Ok(code)
241 }
242}
243
244#[derive(Debug)]
246pub struct Writer {
247 file: File,
248 table: Table,
249 generation: u64,
250 order: Vec<(u64, u64)>,
251 next_order: u64,
252 dictionaries: Vec<Option<GlobalDictionary>>,
253 pending: Vec<PendingStripe>,
254}
255
256#[derive(Debug)]
257struct PendingStripe {
258 order: (u64, u64),
259 rows: usize,
260 pages: Vec<Vec<u8>>,
261 zone: Zone,
262}
263
264const EXTENT_STRIPES: usize = 32;
265
266impl Writer {
267 pub fn create(
273 path: impl AsRef<Path>,
274 name: impl Into<String>,
275 fields: Vec<Field>,
276 ) -> Result<Self> {
277 for field in &fields {
278 type_tag(&field.ty)?;
279 }
280 let mut file =
281 OpenOptions::new().write(true).read(true).create_new(true).open(path).map_err(io)?;
282 let mut header = [0; HEADER as usize];
283 header[..8].copy_from_slice(MAGIC);
284 header[8..12].copy_from_slice(&7_u32.to_le_bytes());
285 file.write_all(&header).map_err(io)?;
286 Ok(Self {
287 file,
288 dictionaries: fields
289 .iter()
290 .map(|field| (field.ty == LogicalType::Varchar).then(GlobalDictionary::new))
291 .collect(),
292 table: Table {
293 name: name.into(),
294 dictionaries: vec![None; fields.len()],
295 fields,
296 stripes: Vec::new(),
297 rows: 0,
298 },
299 generation: 1,
300 order: Vec::new(),
301 next_order: 0,
302 pending: Vec::with_capacity(EXTENT_STRIPES),
303 })
304 }
305
306 pub fn append(&mut self, chunk: &Chunk) -> Result<()> {
312 let order = (self.next_order, 0);
313 self.next_order = self.next_order.saturating_add(1);
314 self.append_at(order, chunk)
315 }
316
317 pub fn append_at(&mut self, order: (u64, u64), chunk: &Chunk) -> Result<()> {
327 if chunk.is_empty() {
328 return Ok(());
329 }
330 if chunk.width() != self.table.fields.len() {
331 return Err(invalid("chunk width differs from table schema"));
332 }
333 let mut pages = Vec::with_capacity(chunk.width());
334 for (index, field) in self.table.fields.iter().enumerate() {
335 let column = chunk.column(index)?;
336 if column.logical_type() != &field.ty {
337 return Err(invalid("chunk type differs from table schema"));
338 }
339 let bytes = encode(column, self.dictionaries[index].as_mut())?;
340 if bytes.len() > MAX_PAGE {
341 return Err(invalid("column page exceeds the configured bound"));
342 }
343 pages.push(bytes);
344 }
345 self.table.rows = self
346 .table
347 .rows
348 .checked_add(chunk.len())
349 .ok_or_else(|| invalid("row count overflow"))?;
350 self.pending.push(PendingStripe { order, rows: chunk.len(), pages, zone: Zone::of(chunk) });
351 if self.pending.len() == EXTENT_STRIPES {
352 self.flush_pending()?;
353 }
354 Ok(())
355 }
356
357 fn flush_pending(&mut self) -> Result<()> {
359 if self.pending.is_empty() {
360 return Ok(());
361 }
362 let width = self.table.fields.len();
363 let mut pages = vec![Vec::with_capacity(width); self.pending.len()];
364 for column in 0..width {
365 for (stripe, pending) in self.pending.iter().enumerate() {
366 let bytes = &pending.pages[column];
367 let offset = self.file.stream_position().map_err(io)?;
368 self.file.write_all(bytes).map_err(io)?;
369 pages[stripe].push(Page {
370 offset,
371 length: u32::try_from(bytes.len())
372 .map_err(|_| invalid("page length overflow"))?,
373 hash: checksum(bytes),
374 });
375 }
376 }
377 for (pending, pages) in self.pending.drain(..).zip(pages) {
378 self.table.stripes.push(Stripe { rows: pending.rows, pages, zone: pending.zone });
379 self.order.push(pending.order);
380 }
381 Ok(())
382 }
383
384 pub fn finish(mut self) -> Result<Table> {
390 self.flush_pending()?;
391 let mut stripes = self.order.into_iter().zip(self.table.stripes).collect::<Vec<_>>();
392 stripes.sort_by_key(|(order, _)| *order);
393 self.table.stripes = stripes.into_iter().map(|(_, stripe)| stripe).collect();
394 for (index, dictionary) in self.dictionaries.into_iter().enumerate() {
395 let Some(dictionary) = dictionary else { continue };
396 let encoded = encode_global_dictionary(dictionary)?;
397 let offset = self.file.stream_position().map_err(io)?;
398 self.file.write_all(&encoded.index).map_err(io)?;
399 self.file.write_all(&encoded.payload).map_err(io)?;
400 let length = encoded
401 .index
402 .len()
403 .checked_add(encoded.payload.len())
404 .ok_or_else(|| invalid("dictionary page length overflow"))?;
405 self.table.dictionaries[index] = Some(Page {
406 offset,
407 length: u32::try_from(length)
408 .map_err(|_| invalid("dictionary page length overflow"))?,
409 hash: checksum(&encoded.index),
410 });
411 }
412 let directory = encode_directory(&self.table)?;
413 if directory.len() > MAX_DIRECTORY {
414 return Err(invalid("directory exceeds the configured bound"));
415 }
416 let offset = self.file.stream_position().map_err(io)?;
417 self.file.write_all(&directory).map_err(io)?;
418 self.file.sync_all().map_err(io)?;
419 let slot = Slot {
420 offset,
421 length: u32::try_from(directory.len())
422 .map_err(|_| invalid("directory length overflow"))?,
423 generation: self.generation,
424 hash: checksum(&directory),
425 };
426 self.file.seek(SeekFrom::Start(16)).map_err(io)?;
427 self.file.write_all(&slot.bytes()).map_err(io)?;
428 self.file.sync_all().map_err(io)?;
429 Ok(self.table)
430 }
431}
432
433#[derive(Debug, Clone)]
435pub struct Reader {
436 file: Arc<File>,
437 table: Arc<Table>,
438 dictionaries: Arc<Vec<OnceLock<Arc<Vector>>>>,
439 extents: Arc<Vec<Vec<ExtentPart>>>,
440 extent_cache: Arc<Vec<Mutex<Vec<CachedExtent>>>>,
441}
442
443#[derive(Debug, Clone, Copy, Default)]
444struct ExtentPart {
445 offset: u64,
446 length: usize,
447 page_start: usize,
448}
449
450#[derive(Debug)]
451struct CachedExtent {
452 offset: u64,
453 bytes: Arc<Vec<u8>>,
454}
455
456const CACHED_EXTENTS_PER_COLUMN: usize = 8;
457
458type CrossingCache = OnceLock<Box<[OnceLock<Result<Vec<u8>>>]>>;
459
460#[derive(Debug)]
461struct NativeText {
462 file: Arc<File>,
463 offsets: Vec<u32>,
464 payload: u64,
465 payload_len: usize,
466 hashes: Vec<u64>,
467 payload_blocks: Vec<OnceLock<Result<Vec<u8>>>>,
468 crossing: Vec<CrossingCache>,
469}
470
471const TEXT_PAYLOAD_BLOCK: usize = 64 * 1024;
472const TEXT_CROSSING_BLOCK: usize = 1024;
473
474impl NativeText {
475 fn payload_block(&self, block: usize) -> Result<Option<&[u8]>> {
476 let Some(slot) = self.payload_blocks.get(block) else { return Ok(None) };
477 slot.get_or_init(|| {
478 let start = block
479 .checked_mul(TEXT_PAYLOAD_BLOCK)
480 .ok_or_else(|| invalid("global dictionary block offset overflow"))?;
481 let len = TEXT_PAYLOAD_BLOCK.min(
482 self.payload_len
483 .checked_sub(start)
484 .ok_or_else(|| invalid("global dictionary block starts past payload"))?,
485 );
486 let mut bytes = vec![0; len];
487 read_at(&self.file, self.payload + start as u64, &mut bytes)?;
488 if checksum(&bytes)
489 != *self
490 .hashes
491 .get(block)
492 .ok_or_else(|| invalid("global dictionary block has no checksum"))?
493 {
494 return Err(invalid("global dictionary payload checksum differs"));
495 }
496 Ok(bytes)
497 })
498 .as_ref()
499 .map(|bytes| Some(bytes.as_slice()))
500 .map_err(Clone::clone)
501 }
502}
503
504impl TextSource for NativeText {
505 fn len(&self) -> usize {
506 self.offsets.len().saturating_sub(1)
507 }
508
509 fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
510 let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
511 else {
512 return Ok(None);
513 };
514 if start == end {
515 return Ok(Some(&[]));
516 }
517 let first = start as usize / TEXT_PAYLOAD_BLOCK;
518 let last = (end as usize - 1) / TEXT_PAYLOAD_BLOCK;
519 if first == last {
520 let Some(block) = self.payload_block(first)? else { return Ok(None) };
521 let within = start as usize % TEXT_PAYLOAD_BLOCK;
522 return Ok(block.get(within..within + (end - start) as usize));
523 }
524 let Some(crossing) = self.crossing.get(index / TEXT_CROSSING_BLOCK) else {
525 return Ok(None);
526 };
527 let block = crossing.get_or_init(|| {
528 (0..TEXT_CROSSING_BLOCK).map(|_| OnceLock::new()).collect::<Vec<_>>().into_boxed_slice()
529 });
530 block[index % TEXT_CROSSING_BLOCK]
531 .get_or_init(|| {
532 let mut bytes = Vec::with_capacity((end - start) as usize);
533 for part in first..=last {
534 let source = self
535 .payload_block(part)?
536 .ok_or_else(|| invalid("global dictionary block is missing"))?;
537 let from = if part == first { start as usize % TEXT_PAYLOAD_BLOCK } else { 0 };
538 let to = if part == last {
539 (end as usize - 1) % TEXT_PAYLOAD_BLOCK + 1
540 } else {
541 source.len()
542 };
543 bytes.extend_from_slice(source.get(from..to).ok_or_else(|| {
544 invalid("global dictionary value exceeds its payload block")
545 })?);
546 }
547 Ok(bytes)
548 })
549 .as_ref()
550 .map(|bytes| Some(bytes.as_slice()))
551 .map_err(Clone::clone)
552 }
553
554 fn bytes_len_at(&self, index: usize) -> Result<Option<usize>> {
555 let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
556 else {
557 return Ok(None);
558 };
559 Ok(Some((end - start) as usize))
560 }
561
562 fn footprint(&self) -> usize {
563 self.offsets.capacity() * size_of::<u32>()
564 + self.payload_blocks.capacity() * size_of::<OnceLock<Result<Vec<u8>>>>()
565 + self.hashes.capacity() * size_of::<u64>()
566 + self
567 .payload_blocks
568 .iter()
569 .filter_map(OnceLock::get)
570 .filter_map(|result| result.as_ref().ok())
571 .map(Vec::capacity)
572 .sum::<usize>()
573 + self.crossing.capacity() * size_of::<CrossingCache>()
574 + self
575 .crossing
576 .iter()
577 .filter_map(OnceLock::get)
578 .map(|block| {
579 block.len() * size_of::<OnceLock<Result<Vec<u8>>>>()
580 + block
581 .iter()
582 .filter_map(OnceLock::get)
583 .filter_map(|result| result.as_ref().ok())
584 .map(Vec::capacity)
585 .sum::<usize>()
586 })
587 .sum::<usize>()
588 }
589}
590
591fn extent_parts(table: &Table) -> Result<Vec<Vec<ExtentPart>>> {
593 let mut refs = Vec::with_capacity(table.stripes.len().saturating_mul(table.fields.len()));
594 for (stripe, entry) in table.stripes.iter().enumerate() {
595 for (column, page) in entry.pages.iter().enumerate() {
596 refs.push((page.offset, column, stripe, page.length as usize));
597 }
598 }
599 refs.sort_unstable_by_key(|entry| entry.0);
600 let mut parts = vec![vec![ExtentPart::default(); table.fields.len()]; table.stripes.len()];
601 let mut first = 0;
602 while first < refs.len() {
603 let (offset, column, _, first_len) = refs[first];
604 let mut end = offset
605 .checked_add(first_len as u64)
606 .ok_or_else(|| invalid("column extent range overflow"))?;
607 let mut last = first + 1;
608 while last < refs.len()
609 && last - first < EXTENT_STRIPES
610 && refs[last].1 == column
611 && refs[last].0 == end
612 {
613 end = end
614 .checked_add(refs[last].3 as u64)
615 .ok_or_else(|| invalid("column extent range overflow"))?;
616 last += 1;
617 }
618 let length = usize::try_from(end - offset)
619 .map_err(|_| invalid("column extent length exceeds this platform"))?;
620 for &(_, _, stripe, _) in &refs[first..last] {
621 let page = table.stripes[stripe].pages[column];
622 let page_start = usize::try_from(page.offset - offset)
623 .map_err(|_| invalid("column page offset exceeds this platform"))?;
624 parts[stripe][column] = ExtentPart { offset, length, page_start };
625 }
626 first = last;
627 }
628 Ok(parts)
629}
630
631impl Reader {
632 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
638 let mut file = File::open(path).map_err(io)?;
639 let size = file.metadata().map_err(io)?.len();
640 if size < HEADER {
641 return Err(invalid("file is shorter than its header"));
642 }
643 let mut header = [0; HEADER as usize];
644 file.read_exact(&mut header).map_err(io)?;
645 if &header[..8] != MAGIC || header[8..12] != 7_u32.to_le_bytes() {
646 return Err(invalid("magic or major version is unsupported"));
647 }
648 let mut selected = None;
649 for start in [16, 16 + SLOT_BYTES] {
650 let slot = Slot::read(&header[start..start + SLOT_BYTES]);
651 if slot.generation == 0 || slot.length == 0 || slot.length as usize > MAX_DIRECTORY {
652 continue;
653 }
654 let Some(end) = slot.offset.checked_add(u64::from(slot.length)) else { continue };
655 if slot.offset < HEADER || end > size {
656 continue;
657 }
658 let mut bytes = vec![0; slot.length as usize];
659 file.seek(SeekFrom::Start(slot.offset)).map_err(io)?;
660 file.read_exact(&mut bytes).map_err(io)?;
661 if checksum(&bytes) == slot.hash
662 && selected
663 .as_ref()
664 .is_none_or(|(old, _): &(Slot, Vec<u8>)| old.generation < slot.generation)
665 {
666 selected = Some((slot, bytes));
667 }
668 }
669 let (_, bytes) = selected.ok_or_else(|| invalid("no committed directory slot is valid"))?;
670 let table = decode_directory(&bytes, size)?;
671 let extents = extent_parts(&table)?;
672 let dictionaries = (0..table.fields.len()).map(|_| OnceLock::new()).collect();
673 let extent_cache = (0..table.fields.len())
674 .map(|_| Mutex::new(Vec::with_capacity(CACHED_EXTENTS_PER_COLUMN)))
675 .collect::<Vec<_>>();
676 Ok(Self {
677 file: Arc::new(file),
678 table: Arc::new(table),
679 dictionaries: Arc::new(dictionaries),
680 extents: Arc::new(extents),
681 extent_cache: Arc::new(extent_cache),
682 })
683 }
684
685 #[must_use]
687 pub fn table(&self) -> &Table {
688 &self.table
689 }
690
691 pub fn read(&self, stripe: usize, columns: &[usize]) -> Result<Chunk> {
697 let stripe_index = stripe;
698 let stripe =
699 self.table.stripes.get(stripe).ok_or_else(|| invalid("stripe index out of range"))?;
700 let mut picked = Vec::with_capacity(columns.len());
701 for &column in columns {
702 let field = self
703 .table
704 .fields
705 .get(column)
706 .ok_or_else(|| invalid("column index out of range"))?;
707 let page = stripe.pages.get(column).ok_or_else(|| invalid("stripe page is missing"))?;
708 let part = self
709 .extents
710 .get(stripe_index)
711 .and_then(|parts| parts.get(column))
712 .ok_or_else(|| invalid("column extent is missing"))?;
713 let bytes = if part.length == page.length as usize {
714 let mut bytes = vec![0; page.length as usize];
715 read_at(&self.file, page.offset, &mut bytes)?;
716 Arc::new(bytes)
717 } else {
718 let cached = self.extent_cache[column]
719 .lock()
720 .map_err(|_| invalid("column extent cache is poisoned"))?
721 .iter()
722 .find(|cached| cached.offset == part.offset)
723 .map(|cached| Arc::clone(&cached.bytes));
724 if let Some(bytes) = cached {
725 bytes
726 } else {
727 let mut bytes = vec![0; part.length];
728 read_at(&self.file, part.offset, &mut bytes)?;
729 let bytes = Arc::new(bytes);
730 let mut cache = self.extent_cache[column]
731 .lock()
732 .map_err(|_| invalid("column extent cache is poisoned"))?;
733 if let Some(cached) = cache.iter().find(|cached| cached.offset == part.offset) {
734 Arc::clone(&cached.bytes)
735 } else {
736 if cache.len() == CACHED_EXTENTS_PER_COLUMN {
737 cache.remove(0);
738 }
739 cache.push(CachedExtent { offset: part.offset, bytes: Arc::clone(&bytes) });
740 bytes
741 }
742 }
743 };
744 let end = part
745 .page_start
746 .checked_add(page.length as usize)
747 .ok_or_else(|| invalid("column page range overflow"))?;
748 let page_bytes = bytes
749 .get(part.page_start..end)
750 .ok_or_else(|| invalid("column page exceeds its extent"))?;
751 if checksum(page_bytes) != page.hash {
752 return Err(invalid("column page checksum differs"));
753 }
754 let dictionary = match self.table.dictionaries[column] {
755 None => None,
756 Some(dictionary_page) => match self.dictionaries[column].get() {
757 Some(dictionary) => Some(Arc::clone(dictionary)),
758 None => {
759 let dictionary = Arc::new(open_global_dictionary(
760 Arc::clone(&self.file),
761 dictionary_page,
762 &field.ty,
763 )?);
764 let _ = self.dictionaries[column].set(Arc::clone(&dictionary));
765 Some(self.dictionaries[column].get().map_or(dictionary, Arc::clone))
766 }
767 },
768 };
769 picked.push(decode(&field.ty, stripe.rows, page_bytes, dictionary)?);
770 }
771 Chunk::with_rows(picked, stripe.rows)
772 }
773
774 #[must_use]
776 pub fn skips(&self, stripe: usize, probes: &[Probe]) -> bool {
777 self.table.stripes.get(stripe).is_some_and(|stripe| stripe.zone.skips(probes))
778 }
779}
780
781#[cfg(unix)]
782fn read_at(file: &File, mut offset: u64, mut bytes: &mut [u8]) -> Result<()> {
783 use std::os::unix::fs::FileExt;
784 while !bytes.is_empty() {
785 let read = file.read_at(bytes, offset).map_err(io)?;
786 if read == 0 {
787 return Err(invalid("column page ends before its declared length"));
788 }
789 offset += read as u64;
790 bytes = &mut bytes[read..];
791 }
792 Ok(())
793}
794
795#[cfg(not(unix))]
796fn read_at(file: &File, offset: u64, bytes: &mut [u8]) -> Result<()> {
797 let mut file = file.try_clone().map_err(io)?;
798 file.seek(SeekFrom::Start(offset)).map_err(io)?;
799 file.read_exact(bytes).map_err(io)
800}
801
802fn type_tag(ty: &LogicalType) -> Result<u8> {
803 match ty {
804 LogicalType::SmallInt => Ok(1),
805 LogicalType::Integer => Ok(2),
806 LogicalType::BigInt => Ok(3),
807 LogicalType::Varchar => Ok(4),
808 LogicalType::Date => Ok(5),
809 LogicalType::Timestamp => Ok(6),
810 LogicalType::Boolean => Ok(7),
811 _ => Err(Error::not_implemented(format!("native storage for {ty}"))),
812 }
813}
814
815fn tag_type(tag: u8) -> Result<LogicalType> {
816 match tag {
817 1 => Ok(LogicalType::SmallInt),
818 2 => Ok(LogicalType::Integer),
819 3 => Ok(LogicalType::BigInt),
820 4 => Ok(LogicalType::Varchar),
821 5 => Ok(LogicalType::Date),
822 6 => Ok(LogicalType::Timestamp),
823 7 => Ok(LogicalType::Boolean),
824 _ => Err(invalid("column type tag is unknown")),
825 }
826}
827
828fn put_u16(out: &mut Vec<u8>, value: u16) {
829 out.extend_from_slice(&value.to_le_bytes());
830}
831fn put_u32(out: &mut Vec<u8>, value: u32) {
832 out.extend_from_slice(&value.to_le_bytes());
833}
834fn put_u64(out: &mut Vec<u8>, value: u64) {
835 out.extend_from_slice(&value.to_le_bytes());
836}
837
838fn encode_directory(table: &Table) -> Result<Vec<u8>> {
839 let mut out = DIRECTORY.to_vec();
840 let name = table.name.as_bytes();
841 put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("table name too long"))?);
842 out.extend_from_slice(name);
843 put_u16(&mut out, u16::try_from(table.fields.len()).map_err(|_| invalid("too many columns"))?);
844 for field in &table.fields {
845 let name = field.name.as_bytes();
846 put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("column name too long"))?);
847 out.extend_from_slice(name);
848 out.push(type_tag(&field.ty)?);
849 out.push(u8::from(field.not_null));
850 }
851 for dictionary in &table.dictionaries {
852 match dictionary {
853 None => out.push(0),
854 Some(page) => {
855 out.push(1);
856 put_u64(&mut out, page.offset);
857 put_u32(&mut out, page.length);
858 put_u64(&mut out, page.hash);
859 }
860 }
861 }
862 put_u64(&mut out, u64::try_from(table.rows).map_err(|_| invalid("row count overflow"))?);
863 put_u32(&mut out, u32::try_from(table.stripes.len()).map_err(|_| invalid("too many stripes"))?);
864 for stripe in &table.stripes {
865 put_u32(
866 &mut out,
867 u32::try_from(stripe.rows).map_err(|_| invalid("stripe row count overflow"))?,
868 );
869 for page in &stripe.pages {
870 put_u64(&mut out, page.offset);
871 put_u32(&mut out, page.length);
872 put_u64(&mut out, page.hash);
873 }
874 for range in stripe.zone.columns() {
875 put_bound(&mut out, range.low.as_ref())?;
876 put_bound(&mut out, range.high.as_ref())?;
877 put_u32(
878 &mut out,
879 u32::try_from(range.nulls).map_err(|_| invalid("null count overflow"))?,
880 );
881 }
882 }
883 Ok(out)
884}
885
886struct Cursor<'a> {
887 bytes: &'a [u8],
888 at: usize,
889}
890impl<'a> Cursor<'a> {
891 fn take(&mut self, len: usize) -> Result<&'a [u8]> {
892 let end = self.at.checked_add(len).ok_or_else(|| invalid("directory offset overflow"))?;
893 let bytes =
894 self.bytes.get(self.at..end).ok_or_else(|| invalid("directory is truncated"))?;
895 self.at = end;
896 Ok(bytes)
897 }
898 fn u8(&mut self) -> Result<u8> {
899 Ok(self.take(1)?[0])
900 }
901 fn u16(&mut self) -> Result<u16> {
902 Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("two bytes")))
903 }
904 fn u32(&mut self) -> Result<u32> {
905 Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("four bytes")))
906 }
907 fn u64(&mut self) -> Result<u64> {
908 Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("eight bytes")))
909 }
910 fn bound(&mut self) -> Result<Option<Bound>> {
911 Ok(match self.u8()? {
912 0 => None,
913 1 => Some(Bound::Int(i128::from_le_bytes(
914 self.take(16)?.try_into().expect("sixteen bytes"),
915 ))),
916 2 => Some(Bound::Real(f64::from_le_bytes(
917 self.take(8)?.try_into().expect("eight bytes"),
918 ))),
919 3 => {
920 let length = self.u32()? as usize;
921 Some(Bound::Bytes(self.take(length)?.to_vec()))
922 }
923 _ => return Err(invalid("bound tag differs")),
924 })
925 }
926 fn text(&mut self) -> Result<String> {
927 let len = self.u16()? as usize;
928 String::from_utf8(self.take(len)?.to_vec()).map_err(|_| invalid("name is not UTF-8"))
929 }
930}
931
932fn decode_directory(bytes: &[u8], size: u64) -> Result<Table> {
933 let mut cur = Cursor { bytes, at: 0 };
934 if cur.take(8)? != DIRECTORY {
935 return Err(invalid("directory magic differs"));
936 }
937 let name = cur.text()?;
938 let width = cur.u16()? as usize;
939 let mut fields = Vec::with_capacity(width);
940 for _ in 0..width {
941 let name = cur.text()?;
942 let ty = tag_type(cur.u8()?)?;
943 let not_null = match cur.u8()? {
944 0 => false,
945 1 => true,
946 _ => return Err(invalid("nullability flag differs")),
947 };
948 fields.push(Field { name, ty, not_null });
949 }
950 let mut dictionaries = Vec::with_capacity(width);
951 for _ in 0..width {
952 dictionaries.push(match cur.u8()? {
953 0 => None,
954 1 => {
955 let page = Page { offset: cur.u64()?, length: cur.u32()?, hash: cur.u64()? };
956 let end = page
957 .offset
958 .checked_add(u64::from(page.length))
959 .ok_or_else(|| invalid("dictionary page offset overflow"))?;
960 if page.offset < HEADER || end > size {
965 return Err(invalid("dictionary page range is outside the file"));
966 }
967 Some(page)
968 }
969 _ => return Err(invalid("dictionary page tag differs")),
970 });
971 }
972 let rows = usize::try_from(cur.u64()?).map_err(|_| invalid("row count does not fit"))?;
973 let count = cur.u32()? as usize;
974 let mut stripes = Vec::with_capacity(count);
975 let mut total = 0_usize;
976 for _ in 0..count {
977 let stripe_rows = cur.u32()? as usize;
978 if stripe_rows == 0 {
979 return Err(invalid("empty stripe"));
980 }
981 total =
982 total.checked_add(stripe_rows).ok_or_else(|| invalid("stripe row count overflow"))?;
983 let mut pages = Vec::with_capacity(width);
984 for _ in 0..width {
985 let offset = cur.u64()?;
986 let length = cur.u32()?;
987 let hash = cur.u64()?;
988 let end = offset
989 .checked_add(u64::from(length))
990 .ok_or_else(|| invalid("page offset overflow"))?;
991 if offset < HEADER || end > size || length as usize > MAX_PAGE {
992 return Err(invalid("page range is outside the file"));
993 }
994 pages.push(Page { offset, length, hash });
995 }
996 let mut ranges = Vec::with_capacity(width);
997 for _ in 0..width {
998 let low = cur.bound()?;
999 let high = cur.bound()?;
1000 let nulls = cur.u32()? as usize;
1001 if nulls > stripe_rows {
1002 return Err(invalid("null count exceeds stripe rows"));
1003 }
1004 ranges.push(Range { low, high, nulls });
1005 }
1006 stripes.push(Stripe { rows: stripe_rows, pages, zone: Zone::from_ranges(ranges) });
1007 }
1008 if total != rows {
1009 return Err(invalid("table row count differs from stripes"));
1010 }
1011 if cur.at != bytes.len() {
1012 return Err(invalid("directory has trailing bytes"));
1013 }
1014 Ok(Table { name, fields, stripes, rows, dictionaries })
1015}
1016
1017fn put_bound(out: &mut Vec<u8>, bound: Option<&Bound>) -> Result<()> {
1018 match bound {
1019 None => out.push(0),
1020 Some(Bound::Int(value)) => {
1021 out.push(1);
1022 out.extend_from_slice(&value.to_le_bytes());
1023 }
1024 Some(Bound::Real(value)) => {
1025 out.push(2);
1026 out.extend_from_slice(&value.to_le_bytes());
1027 }
1028 Some(Bound::Bytes(value)) => {
1029 out.push(3);
1030 put_u32(out, u32::try_from(value.len()).map_err(|_| invalid("bound length overflow"))?);
1031 out.extend_from_slice(value);
1032 }
1033 }
1034 Ok(())
1035}
1036
1037fn encode(vector: &Vector, global: Option<&mut GlobalDictionary>) -> Result<Vec<u8>> {
1038 let ty = vector.logical_type();
1039 let flat = vector.flatten()?;
1041 let mut out = Vec::new();
1042 let mut global_codes = None;
1043 if let Some(global) = global {
1044 let mut codes = Vec::with_capacity(flat.len());
1045 for row in 0..flat.len() {
1046 let text = flat.text_at(row).unwrap_or("");
1047 codes.push(global.code(text)?);
1048 }
1049 global_codes = Some(codes);
1050 }
1051 let dictionary = if global_codes.is_none() && ty == &LogicalType::Varchar {
1052 string_dictionary(&flat)?
1053 } else {
1054 None
1055 };
1056 let packed_vector = if dictionary.is_none() && global_codes.is_none() {
1057 Some(flat.bit_packed()?)
1058 } else {
1059 None
1060 };
1061 let packed = packed_vector.as_ref().and_then(Vector::packed_parts);
1062 out.push(if global_codes.is_some() {
1063 3
1064 } else if dictionary.is_some() {
1065 1
1066 } else if packed.is_some() {
1067 2
1068 } else {
1069 0
1070 });
1071 let nulls = flat.validity();
1072 let flag = match nulls {
1073 Validity::AllValid => 0,
1074 Validity::AllInvalid => 1,
1075 Validity::Mask(_) => 2,
1076 };
1077 out.push(flag);
1078 if flag == 2 {
1079 for group in (0..vector.len()).step_by(8) {
1080 let mut bits = 0_u8;
1081 for bit in 0..8 {
1082 if group + bit < vector.len() && !flat.is_null_at(group + bit) {
1083 bits |= 1 << bit;
1084 }
1085 }
1086 out.push(bits);
1087 }
1088 }
1089 if let Some(codes) = global_codes {
1090 for code in codes {
1091 put_u32(&mut out, code);
1092 }
1093 return Ok(out);
1094 }
1095 if let Some(dictionary) = dictionary {
1096 out.extend_from_slice(&dictionary);
1097 return Ok(out);
1098 }
1099 if let Some(packed) = packed {
1100 if packed.offset() != 0 {
1101 return Err(invalid("writer received a sliced packed vector"));
1102 }
1103 out.push(u8::try_from(packed.width()).map_err(|_| invalid("packed width overflow"))?);
1104 out.extend_from_slice(&packed.base().to_le_bytes());
1105 put_u32(
1106 &mut out,
1107 u32::try_from(packed.words().len()).map_err(|_| invalid("too many packed words"))?,
1108 );
1109 for word in packed.words() {
1110 put_u64(&mut out, *word);
1111 }
1112 return Ok(out);
1113 }
1114 let data = flat.data().ok_or_else(|| invalid("scalar column did not flatten"))?;
1115 match (ty, data) {
1116 (LogicalType::SmallInt, Data::Int16(values)) => {
1117 for value in &**values {
1118 out.extend_from_slice(&value.to_le_bytes());
1119 }
1120 }
1121 (LogicalType::Integer | LogicalType::Date, Data::Int32(values)) => {
1122 for value in &**values {
1123 out.extend_from_slice(&value.to_le_bytes());
1124 }
1125 }
1126 (LogicalType::BigInt | LogicalType::Timestamp, Data::Int64(values)) => {
1127 for value in &**values {
1128 out.extend_from_slice(&value.to_le_bytes());
1129 }
1130 }
1131 (LogicalType::Boolean, Data::Bool(values)) => {
1132 for value in &**values {
1133 out.push(u8::from(*value));
1134 }
1135 }
1136 (LogicalType::Varchar, Data::Varlen(values)) => {
1137 let mut bytes = Vec::new();
1138 put_u32(&mut out, 0);
1139 for row in 0..vector.len() {
1140 let value = values.bytes(row).ok_or_else(|| invalid("string view is invalid"))?;
1141 bytes.extend_from_slice(value);
1142 put_u32(
1143 &mut out,
1144 u32::try_from(bytes.len())
1145 .map_err(|_| invalid("string payload exceeds 4GiB"))?,
1146 );
1147 }
1148 out.extend_from_slice(&bytes);
1149 }
1150 _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
1151 }
1152 Ok(out)
1153}
1154
1155fn string_dictionary(vector: &Vector) -> Result<Option<Vec<u8>>> {
1156 let mut by_text = HashMap::new();
1157 let mut values = Vec::new();
1158 let mut codes = Vec::with_capacity(vector.len());
1159 let mut plain_bytes = 0_usize;
1160 for row in 0..vector.len() {
1161 let text = vector.text_at(row).unwrap_or("");
1162 plain_bytes = plain_bytes.saturating_add(text.len());
1163 let code = match by_text.get(text) {
1164 Some(&code) => code,
1165 None => {
1166 let code = u32::try_from(values.len())
1167 .map_err(|_| invalid("too many dictionary values"))?;
1168 by_text.insert(text, code);
1169 values.push(text);
1170 code
1171 }
1172 };
1173 codes.push(code);
1174 }
1175 let dictionary_bytes = values.iter().map(|value| value.len()).sum::<usize>();
1176 let encoded = 8_usize
1177 .saturating_add((values.len() + 1).saturating_mul(4))
1178 .saturating_add(dictionary_bytes)
1179 .saturating_add(codes.len().saturating_mul(4));
1180 let plain = (vector.len() + 1).saturating_mul(4).saturating_add(plain_bytes);
1181 if encoded >= plain {
1182 return Ok(None);
1183 }
1184 let mut out = Vec::with_capacity(encoded);
1185 put_u32(
1186 &mut out,
1187 u32::try_from(values.len()).map_err(|_| invalid("too many dictionary values"))?,
1188 );
1189 put_u32(
1190 &mut out,
1191 u32::try_from(dictionary_bytes).map_err(|_| invalid("dictionary payload exceeds 4GiB"))?,
1192 );
1193 let mut offset = 0_u32;
1194 put_u32(&mut out, offset);
1195 for value in &values {
1196 offset = offset
1197 .checked_add(
1198 u32::try_from(value.len()).map_err(|_| invalid("dictionary value is too long"))?,
1199 )
1200 .ok_or_else(|| invalid("dictionary payload exceeds 4GiB"))?;
1201 put_u32(&mut out, offset);
1202 }
1203 for value in values {
1204 out.extend_from_slice(value.as_bytes());
1205 }
1206 for code in codes {
1207 put_u32(&mut out, code);
1208 }
1209 Ok(Some(out))
1210}
1211
1212struct EncodedDictionary {
1213 index: Vec<u8>,
1214 payload: Vec<u8>,
1215}
1216
1217fn encode_global_dictionary(dictionary: GlobalDictionary) -> Result<EncodedDictionary> {
1218 let values = dictionary.offsets.len() - 1;
1219 let payload_len = dictionary.payload.len();
1220 let blocks = payload_len.div_ceil(TEXT_PAYLOAD_BLOCK);
1221 let mut index = Vec::with_capacity(12 + (values + 1) * 4 + blocks * 8);
1222 put_u32(
1223 &mut index,
1224 u32::try_from(values).map_err(|_| invalid("global dictionary has too many values"))?,
1225 );
1226 put_u32(&mut index, TEXT_PAYLOAD_BLOCK as u32);
1227 put_u32(
1228 &mut index,
1229 u32::try_from(blocks).map_err(|_| invalid("global dictionary has too many blocks"))?,
1230 );
1231 for offset in dictionary.offsets {
1232 put_u32(&mut index, offset);
1233 }
1234 for block in dictionary.payload.chunks(TEXT_PAYLOAD_BLOCK) {
1235 put_u64(&mut index, checksum(block));
1236 }
1237 Ok(EncodedDictionary { index, payload: dictionary.payload })
1238}
1239
1240fn open_global_dictionary(file: Arc<File>, page: Page, ty: &LogicalType) -> Result<Vector> {
1241 if ty != &LogicalType::Varchar {
1242 return Err(invalid("global dictionary belongs to a non-string column"));
1243 }
1244 let mut header = [0; 12];
1245 read_at(&file, page.offset, &mut header)?;
1246 let count = u32::from_le_bytes(header[0..4].try_into().expect("four bytes")) as usize;
1247 let block_size = u32::from_le_bytes(header[4..8].try_into().expect("four bytes")) as usize;
1248 let blocks = u32::from_le_bytes(header[8..12].try_into().expect("four bytes")) as usize;
1249 if block_size != TEXT_PAYLOAD_BLOCK {
1250 return Err(invalid("global dictionary block width differs"));
1251 }
1252 let offset_len = (count + 1)
1253 .checked_mul(4)
1254 .ok_or_else(|| invalid("global dictionary offset count overflow"))?;
1255 let hash_len =
1256 blocks.checked_mul(8).ok_or_else(|| invalid("global dictionary block count overflow"))?;
1257 let index_len = 12usize
1258 .checked_add(offset_len)
1259 .and_then(|len| len.checked_add(hash_len))
1260 .ok_or_else(|| invalid("global dictionary header overflow"))?;
1261 if index_len > page.length as usize {
1262 return Err(invalid("global dictionary offset index exceeds its page"));
1263 }
1264 let mut index = vec![0; index_len];
1265 index[..12].copy_from_slice(&header);
1266 read_at(&file, page.offset + 12, &mut index[12..])?;
1267 if checksum(&index) != page.hash {
1268 return Err(invalid("global dictionary index checksum differs"));
1269 }
1270 let offsets = index[12..12 + offset_len]
1271 .chunks_exact(4)
1272 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
1273 .collect::<Vec<_>>();
1274 let hashes = index[12 + offset_len..]
1275 .chunks_exact(8)
1276 .map(|part| u64::from_le_bytes(part.try_into().expect("eight bytes")))
1277 .collect::<Vec<_>>();
1278 let payload_len = page.length as usize - index_len;
1279 if blocks != payload_len.div_ceil(TEXT_PAYLOAD_BLOCK) {
1280 return Err(invalid("global dictionary block count differs from its payload"));
1281 }
1282 if offsets.first() != Some(&0)
1283 || offsets.last().copied().map(|last| last as usize) != Some(payload_len)
1284 || offsets.windows(2).any(|pair| pair[0] > pair[1])
1285 {
1286 return Err(invalid("global dictionary offsets do not bound the payload"));
1287 }
1288 let payload_blocks =
1289 (0..payload_len.div_ceil(TEXT_PAYLOAD_BLOCK)).map(|_| OnceLock::new()).collect();
1290 let crossing = (0..count.div_ceil(TEXT_CROSSING_BLOCK)).map(|_| OnceLock::new()).collect();
1291 Vector::external_text(
1292 LogicalType::Varchar,
1293 Arc::new(NativeText {
1294 file,
1295 offsets,
1296 payload: page.offset + index_len as u64,
1297 payload_len,
1298 hashes,
1299 payload_blocks,
1300 crossing,
1301 }),
1302 )
1303}
1304
1305fn decode(
1306 ty: &LogicalType,
1307 rows: usize,
1308 bytes: &[u8],
1309 global: Option<Arc<Vector>>,
1310) -> Result<Vector> {
1311 let mut cur = Cursor { bytes, at: 0 };
1312 let codec = cur.u8()?;
1313 let flag = cur.u8()?;
1314 let validity = match flag {
1315 0 => Validity::AllValid,
1316 1 => Validity::AllInvalid,
1317 2 => {
1318 let mask = cur.take(rows.div_ceil(8))?;
1319 Validity::from_iter(rows, |row| mask[row / 8] >> (row % 8) & 1 == 1)
1320 }
1321 _ => return Err(invalid("page validity tag differs")),
1322 };
1323 if codec == 1 {
1324 if ty != &LogicalType::Varchar {
1325 return Err(invalid("dictionary codec belongs to a non-string page"));
1326 }
1327 let count = cur.u32()? as usize;
1328 let payload_len = cur.u32()? as usize;
1329 let offset_bytes = cur.take(
1330 (count + 1)
1331 .checked_mul(4)
1332 .ok_or_else(|| invalid("dictionary offset count overflow"))?,
1333 )?;
1334 let offsets = offset_bytes
1335 .chunks_exact(4)
1336 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
1337 .collect::<Vec<_>>();
1338 let payload = cur.take(payload_len)?.to_vec();
1339 if offsets.first() != Some(&0)
1340 || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
1341 || offsets.windows(2).any(|pair| pair[0] > pair[1])
1342 {
1343 return Err(invalid("dictionary offsets do not bound the payload"));
1344 }
1345 let mut strings = StringColumn::over(Buffer::from_vec(payload));
1346 for pair in offsets.windows(2) {
1347 strings.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
1348 }
1349 let mut codes = Vec::with_capacity(rows);
1350 for _ in 0..rows {
1351 codes.push(cur.u32()?);
1352 }
1353 if codes.iter().any(|code| *code as usize >= count) {
1354 return Err(invalid("dictionary code is out of range"));
1355 }
1356 if cur.at != bytes.len() {
1357 return Err(invalid("dictionary page has trailing bytes"));
1358 }
1359 let dictionary = Vector::flat(LogicalType::Varchar, Data::Varlen(strings))?;
1360 return Ok(Vector::dictionary(codes, dictionary)?.with_validity(validity));
1361 }
1362 if codec == 3 {
1363 let dictionary = global.ok_or_else(|| invalid("global code page has no dictionary"))?;
1364 let mut codes = Vec::with_capacity(rows);
1365 let mut highest = None;
1366 for _ in 0..rows {
1367 let code = cur.u32()?;
1368 highest = Some(highest.map_or(code, |old: u32| old.max(code)));
1369 codes.push(code);
1370 }
1371 if cur.at != bytes.len() {
1372 return Err(invalid("global code page has trailing bytes"));
1373 }
1374 return Ok(Vector::stable_dictionary_validated(codes, dictionary, highest)?
1375 .with_validity(validity));
1376 }
1377 if codec == 2 {
1378 let width = u32::from(cur.u8()?);
1379 let base = i128::from_le_bytes(cur.take(16)?.try_into().expect("sixteen bytes"));
1380 let count = cur.u32()? as usize;
1381 let mut words = Vec::with_capacity(count);
1382 for _ in 0..count {
1383 words.push(cur.u64()?);
1384 }
1385 if cur.at != bytes.len() {
1386 return Err(invalid("packed page has trailing bytes"));
1387 }
1388 return Ok(Vector::packed(ty.clone(), words, width, base, rows)?.with_validity(validity));
1389 }
1390 if codec != 0 {
1391 return Err(invalid("page codec is unknown"));
1392 }
1393 let data = match ty {
1394 LogicalType::SmallInt => {
1395 let values =
1396 cur.take(rows.checked_mul(2).ok_or_else(|| invalid("page size overflow"))?)?;
1397 Data::Int16(
1398 values
1399 .chunks_exact(2)
1400 .map(|item| i16::from_le_bytes(item.try_into().expect("two bytes")))
1401 .collect::<Vec<_>>()
1402 .into(),
1403 )
1404 }
1405 LogicalType::Integer | LogicalType::Date => {
1406 let values =
1407 cur.take(rows.checked_mul(4).ok_or_else(|| invalid("page size overflow"))?)?;
1408 Data::Int32(
1409 values
1410 .chunks_exact(4)
1411 .map(|item| i32::from_le_bytes(item.try_into().expect("four bytes")))
1412 .collect::<Vec<_>>()
1413 .into(),
1414 )
1415 }
1416 LogicalType::BigInt | LogicalType::Timestamp => {
1417 let values =
1418 cur.take(rows.checked_mul(8).ok_or_else(|| invalid("page size overflow"))?)?;
1419 Data::Int64(
1420 values
1421 .chunks_exact(8)
1422 .map(|item| i64::from_le_bytes(item.try_into().expect("eight bytes")))
1423 .collect::<Vec<_>>()
1424 .into(),
1425 )
1426 }
1427 LogicalType::Boolean => {
1428 let values = cur.take(rows)?;
1429 if values.iter().any(|value| *value > 1) {
1430 return Err(invalid("boolean page has another value"));
1431 }
1432 Data::Bool(values.iter().map(|value| *value == 1).collect::<Vec<_>>().into())
1433 }
1434 LogicalType::Varchar => {
1435 let offset_bytes = cur
1436 .take((rows + 1).checked_mul(4).ok_or_else(|| invalid("offset count overflow"))?)?;
1437 let offsets = offset_bytes
1438 .chunks_exact(4)
1439 .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
1440 .collect::<Vec<_>>();
1441 let payload = cur.take(bytes.len() - cur.at)?.to_vec();
1442 if offsets.first() != Some(&0)
1443 || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
1444 || offsets.windows(2).any(|pair| pair[0] > pair[1])
1445 {
1446 return Err(invalid("string offsets do not bound the payload"));
1447 }
1448 let mut values = StringColumn::over(Buffer::from_vec(payload));
1449 for pair in offsets.windows(2) {
1450 values.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
1451 }
1452 Data::Varlen(values)
1453 }
1454 _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
1455 };
1456 if cur.at != bytes.len() {
1457 return Err(invalid("page has trailing bytes"));
1458 }
1459 Ok(Vector::flat(ty.clone(), data)?.with_validity(validity))
1460}
1461
1462#[cfg(test)]
1463mod tests {
1464 use std::fs;
1465 use std::io::{Seek, SeekFrom, Write};
1466 use std::path::PathBuf;
1467 use std::time::{SystemTime, UNIX_EPOCH};
1468
1469 use rudb_common::Value;
1470 use rudb_common::bounds::Op;
1471
1472 use super::*;
1473
1474 #[test]
1475 fn checksum_matches_fixed_vectors() {
1476 assert_eq!(checksum(b""), 0xef46_db37_51d8_e999);
1477 assert_eq!(checksum(b"a"), 0xd24e_c4f1_a98c_6e5b);
1478 assert_eq!(checksum(b"abc"), 0x44bc_2cf5_ad77_0999);
1479 }
1480
1481 fn path(label: &str) -> PathBuf {
1482 let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
1483 std::env::temp_dir().join(format!("rudb-native-{label}-{}-{stamp}.rdb", std::process::id()))
1484 }
1485
1486 fn sample() -> Chunk {
1487 Chunk::new(vec![
1488 Vector::from_values(
1489 LogicalType::Integer,
1490 &[Value::Integer(4), Value::Integer(9), Value::Integer(-2)],
1491 )
1492 .expect("integers"),
1493 Vector::from_values(
1494 LogicalType::Varchar,
1495 &[
1496 Value::Varchar("alpha".into()),
1497 Value::Null,
1498 Value::Varchar("long text after a slash".into()),
1499 ],
1500 )
1501 .expect("strings"),
1502 ])
1503 .expect("matching rows")
1504 }
1505
1506 #[test]
1507 fn committed_file_reopens_and_reads_only_requested_columns() {
1508 let path = path("reopen");
1509 let mut writer = Writer::create(
1510 &path,
1511 "items",
1512 vec![
1513 Field::required("id", LogicalType::Integer),
1514 Field::new("text", LogicalType::Varchar),
1515 ],
1516 )
1517 .expect("new file");
1518 writer.append(&sample()).expect("first stripe");
1519 writer.append(&sample()).expect("second stripe");
1520 writer.finish().expect("commit");
1521 let reader = Reader::open(&path).expect("reopen from disk");
1522 assert_eq!(reader.table().rows(), 6);
1523 assert_eq!(reader.table().stripes().len(), 2);
1524 let text = reader.read(1, &[1]).expect("only text page");
1525 assert_eq!(text.width(), 1);
1526 assert_eq!(text.value_at(1, 0), Value::Null);
1527 assert_eq!(text.value_at(2, 0), Value::Varchar("long text after a slash".into()));
1528 let count = reader.read(0, &[]).expect("no page is needed for count");
1529 assert_eq!(count.len(), 3);
1530 assert!(reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(100) }]));
1531 assert!(!reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(0) }]));
1532 fs::remove_file(path).expect("remove scratch file");
1533 }
1534
1535 #[test]
1536 fn an_unfinished_or_damaged_file_does_not_answer_with_partial_rows() {
1537 let unfinished = path("unfinished");
1538 let mut writer =
1539 Writer::create(&unfinished, "items", vec![Field::new("id", LogicalType::Integer)])
1540 .expect("new file");
1541 let chunk = Chunk::new(vec![
1542 Vector::flat(LogicalType::Integer, Data::Int32(vec![1, 2, 3].into()))
1543 .expect("integers"),
1544 ])
1545 .expect("chunk");
1546 writer.append(&chunk).expect("page written");
1547 drop(writer);
1548 assert!(Reader::open(&unfinished).is_err(), "no directory was committed");
1549 fs::remove_file(unfinished).expect("remove scratch file");
1550
1551 let damaged = path("damaged");
1552 let mut writer =
1553 Writer::create(&damaged, "items", vec![Field::new("id", LogicalType::Integer)])
1554 .expect("new file");
1555 writer.append(&chunk).expect("page written");
1556 writer.finish().expect("commit");
1557 let reader = Reader::open(&damaged).expect("valid directory");
1558 let mut file =
1559 OpenOptions::new().write(true).open(&damaged).expect("open for a damaged page");
1560 file.seek(SeekFrom::Start(HEADER + 1)).expect("inside first page");
1561 file.write_all(&[255]).expect("damage one byte");
1562 assert!(reader.read(0, &[0]).is_err(), "page checksum rejects corruption");
1563 fs::remove_file(damaged).expect("remove scratch file");
1564 }
1565
1566 #[test]
1567 fn damaged_lazy_dictionary_payload_is_an_error() {
1568 let path = path("damaged-dictionary");
1569 let mut writer = Writer::create(
1570 &path,
1571 "items",
1572 vec![
1573 Field::required("id", LogicalType::Integer),
1574 Field::new("text", LogicalType::Varchar),
1575 ],
1576 )
1577 .expect("new file");
1578 writer.append(&sample()).expect("stripe written");
1579 writer.finish().expect("commit");
1580
1581 let reader = Reader::open(&path).expect("valid directory");
1582 let dictionary = reader.table.dictionaries[1].expect("string dictionary page");
1583 let index_len = 12_u64 + 4 * 4 + 8;
1584 let mut file = OpenOptions::new().write(true).open(&path).expect("open dictionary page");
1585 file.seek(SeekFrom::Start(dictionary.offset + index_len))
1586 .expect("inside dictionary payload");
1587 file.write_all(&[255]).expect("damage dictionary payload");
1588
1589 let chunk = reader.read(0, &[1]).expect("code page and dictionary index remain valid");
1590 let error =
1591 chunk.validate_external().expect_err("payload corruption must reach the caller");
1592 assert!(error.message().contains("payload checksum differs"), "{error}");
1593 fs::remove_file(path).expect("remove scratch file");
1594 }
1595
1596 #[test]
1597 fn a_global_dictionary_may_be_larger_than_one_column_page() {
1598 let dictionary = Page {
1599 offset: HEADER,
1600 length: u32::try_from(MAX_PAGE + 1).expect("the page bound fits on disk"),
1601 hash: 0,
1602 };
1603 let table = Table {
1604 name: "items".to_owned(),
1605 fields: vec![Field::new("text", LogicalType::Varchar)],
1606 stripes: Vec::new(),
1607 rows: 0,
1608 dictionaries: vec![Some(dictionary)],
1609 };
1610 let directory = encode_directory(&table).expect("directory");
1611 let file_size = dictionary.offset + u64::from(dictionary.length) + 1;
1612
1613 let decoded = decode_directory(&directory, file_size).expect("large lazy dictionary");
1614 assert_eq!(decoded.dictionaries[0].expect("dictionary").length, dictionary.length);
1615 }
1616}