Skip to main content

rudb_native/
lib.rs

1//! Rudb's single-file columnar snapshot format.
2//!
3//! A committed directory names independently readable column pages. The first version handles
4//! scalar columns and one table; the file header already has two generation slots so an unfinished
5//! replacement directory cannot hide the last complete one.
6
7#![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/// One independently readable stripe of a table.
134#[derive(Debug, Clone)]
135pub struct Stripe {
136    rows: usize,
137    pages: Vec<Page>,
138    zone: Zone,
139}
140
141impl Stripe {
142    /// Number of rows in this stripe.
143    #[must_use]
144    pub fn rows(&self) -> usize {
145        self.rows
146    }
147}
148
149/// The committed table directory.
150#[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    /// The SQL table name held by this snapshot.
161    #[must_use]
162    pub fn name(&self) -> &str {
163        &self.name
164    }
165
166    /// Columns in their SQL order.
167    #[must_use]
168    pub fn fields(&self) -> &[Field] {
169        &self.fields
170    }
171
172    /// Committed row count.
173    #[must_use]
174    pub fn rows(&self) -> usize {
175        self.rows
176    }
177
178    /// Independently readable stripes.
179    #[must_use]
180    pub fn stripes(&self) -> &[Stripe] {
181        &self.stripes
182    }
183}
184
185/// Appends pages and commits a new directory for one table.
186#[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/// Appends pages and commits a new directory for one table.
245#[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    /// Creates a new v7 file and its first table.
268    ///
269    /// # Errors
270    ///
271    /// If the file exists, a field has no v7 scalar encoding, or the path cannot be written.
272    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    /// Writes one chunk as independently readable column pages.
307    ///
308    /// # Errors
309    ///
310    /// If its width or types differ from the declared table, or a page exceeds its bound.
311    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    /// Writes one chunk and records its source position for directory ordering.
318    ///
319    /// Pages may be encoded by parallel pipeline instances and reach the file in completion order.
320    /// Their directory entries are sorted by this key at commit, so a scan still observes source
321    /// order without holding the page bytes until earlier work finishes.
322    ///
323    /// # Errors
324    ///
325    /// The same as [`Self::append`].
326    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    /// Writes one bounded group of stripes with each column contiguous on disk.
358    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    /// Commits the directory and syncs the file before publishing its header slot.
385    ///
386    /// # Errors
387    ///
388    /// If directory encoding, writing, or syncing fails.
389    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/// Reads committed native column pages without holding the table in memory.
434#[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
591/// Maps each logical page to the bounded contiguous read that contains it.
592fn 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    /// Opens the highest valid directory slot.
633    ///
634    /// # Errors
635    ///
636    /// If the file has no valid committed directory or a directory pointer is out of bounds.
637    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    /// The committed table directory.
686    #[must_use]
687    pub fn table(&self) -> &Table {
688        &self.table
689    }
690
691    /// Reads only the named columns from one stripe.
692    ///
693    /// # Errors
694    ///
695    /// If a stripe, column, page, or checksum is invalid.
696    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    /// Whether persisted bounds prove that a stripe cannot match the predicates.
775    #[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                // A global dictionary covers a whole column, not one bounded stripe. Its lazy
961                // payload is intentionally allowed to grow past `MAX_PAGE`; only ordinary column
962                // pages are capped there. `Writer::finish` has already bounded this length by the
963                // on-disk `u32`, and the range check below keeps it inside the file.
964                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    // flatten: the file writer needs a uniform scalar page and does it once per loaded chunk.
1040    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}