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