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::TinyInt
484                | LogicalType::SmallInt
485                | LogicalType::Integer
486                | LogicalType::BigInt
487                | LogicalType::UTinyInt
488                | LogicalType::USmallInt
489                | LogicalType::UInteger
490                | LogicalType::UBigInt
491                | LogicalType::Date
492                | LogicalType::Timestamp
493        ) {
494            return Ok(None);
495        }
496        let mut candidates: HashMap<FrequencyValue, u32> = HashMap::new();
497        let mut decrements = 0_u64;
498        self.visit_numeric(column, |_, value| {
499            if let Some(count) = candidates.get_mut(&value) {
500                *count = count.saturating_add(1);
501            } else if candidates.len() < FREQUENCY_CANDIDATES {
502                candidates.insert(value, 1);
503            } else {
504                candidates.retain(|_, count| {
505                    *count -= 1;
506                    *count != 0
507                });
508                decrements = decrements.saturating_add(1);
509            }
510        })?;
511        let (exact, ordinals) = if decrements == 0 {
512            (
513                candidates
514                    .into_iter()
515                    .map(|(value, count)| (value, u64::from(count)))
516                    .collect::<HashMap<_, _>>(),
517                Vec::new(),
518            )
519        } else {
520            let mut lower = candidates.values().copied().collect::<Vec<_>>();
521            lower.sort_unstable_by(|left, right| right.cmp(left));
522            if lower.len() < FREQUENCY_BUILD_RANK
523                || u64::from(lower[FREQUENCY_BUILD_RANK - 1]) <= decrements
524            {
525                return Ok(None);
526            }
527            let mut exact =
528                candidates.into_keys().map(|value| (value, 0_u64)).collect::<HashMap<_, _>>();
529            let mut ordinals = Vec::new();
530            let mut exceeded = false;
531            self.visit_numeric(column, |ordinal, value| {
532                if let Some(count) = exact.get_mut(&value) {
533                    *count = count.saturating_add(1);
534                    if !exceeded {
535                        if ordinals.len() < FREQUENCY_ORDINALS {
536                            ordinals.push(ordinal);
537                        } else {
538                            ordinals.clear();
539                            exceeded = true;
540                        }
541                    }
542                }
543            })?;
544            (exact, ordinals)
545        };
546        let mut entries = exact
547            .into_iter()
548            .map(|(value, count)| FrequencyEntry { value, count })
549            .collect::<Vec<_>>();
550        entries.sort_unstable_by(|left, right| {
551            right.count.cmp(&left.count).then_with(|| frequency_order(left.value, right.value))
552        });
553        let omitted_max =
554            entries.get(FREQUENCY_ENTRIES).map_or(decrements, |entry| decrements.max(entry.count));
555        entries.truncate(FREQUENCY_ENTRIES);
556        Ok(Some(FrequencySummary { entries, omitted_max, ordinals }))
557    }
558
559    fn visit_numeric(
560        &self,
561        column: usize,
562        mut visit: impl FnMut(u64, FrequencyValue),
563    ) -> Result<()> {
564        let ty = &self.table.fields[column].ty;
565        let mut start = 0_u64;
566        for stripe in &self.table.stripes {
567            let page = stripe.pages[column];
568            let mut bytes = vec![0; page.length as usize];
569            read_at(&self.file, page.offset, &mut bytes)?;
570            if checksum(&bytes) != page.hash {
571                return Err(invalid("column page checksum differs while building frequencies"));
572            }
573            let vector = decode(ty, stripe.rows, &bytes, None)?;
574            // row at a time: frequency construction visits decoded values to update bounded candidates.
575            for row in 0..stripe.rows {
576                let value = if vector.is_null_at(row) {
577                    FrequencyValue::Null
578                } else {
579                    // An unsigned column has no signed reading, and the documented fallback is the
580                    // value itself. Every unsigned width the format stores fits in the `i128` a
581                    // candidate is keyed by, so nothing is lost on the way through.
582                    let widened = match vector.signed_at(row) {
583                        Some(value) => Some(value),
584                        None => match vector.value_at(row) {
585                            Value::UTinyInt(value) => Some(i128::from(value)),
586                            Value::USmallInt(value) => Some(i128::from(value)),
587                            Value::UInteger(value) => Some(i128::from(value)),
588                            Value::UBigInt(value) => Some(i128::from(value)),
589                            _ => None,
590                        },
591                    };
592                    FrequencyValue::Integer(widened.ok_or_else(|| {
593                        invalid("numeric frequency page did not contain an integer value")
594                    })?)
595                };
596                visit(start.saturating_add(row as u64), value);
597            }
598            start = start.saturating_add(stripe.rows as u64);
599        }
600        Ok(())
601    }
602
603    /// Builds independent numeric synopses concurrently after all column pages are committed.
604    fn numeric_frequencies(&self) -> Result<Vec<Option<FrequencySummary>>> {
605        let columns = self
606            .table
607            .fields
608            .iter()
609            .enumerate()
610            .filter_map(|(column, field)| {
611                matches!(
612                    field.ty,
613                    LogicalType::TinyInt
614                        | LogicalType::SmallInt
615                        | LogicalType::Integer
616                        | LogicalType::BigInt
617                        | LogicalType::UTinyInt
618                        | LogicalType::USmallInt
619                        | LogicalType::UInteger
620                        | LogicalType::UBigInt
621                        | LogicalType::Date
622                        | LogicalType::Timestamp
623                )
624                .then_some(column)
625            })
626            .collect::<Vec<_>>();
627        let workers = std::thread::available_parallelism()
628            .map_or(1, usize::from)
629            .min(MAX_FREQUENCY_WORKERS)
630            .min(columns.len());
631        if workers <= 1 {
632            let mut frequencies = vec![None; self.table.fields.len()];
633            for column in columns {
634                frequencies[column] = self.numeric_frequency(column)?;
635            }
636            return Ok(frequencies);
637        }
638        let width = columns.len().div_ceil(workers);
639        let pieces = std::thread::scope(|scope| {
640            columns
641                .chunks(width)
642                .map(|columns| {
643                    scope.spawn(|| {
644                        columns
645                            .iter()
646                            .map(|&column| Ok((column, self.numeric_frequency(column)?)))
647                            .collect::<Result<Vec<_>>>()
648                    })
649                })
650                .collect::<Vec<_>>()
651                .into_iter()
652                .map(|handle| {
653                    handle
654                        .join()
655                        .map_err(|_| Error::internal("a native frequency worker panicked"))?
656                })
657                .collect::<Result<Vec<_>>>()
658        })?;
659        let mut frequencies = vec![None; self.table.fields.len()];
660        for piece in pieces {
661            for (column, summary) in piece {
662                frequencies[column] = summary;
663            }
664        }
665        Ok(frequencies)
666    }
667
668    /// Commits the directory and syncs the file before publishing its header slot.
669    ///
670    /// # Errors
671    ///
672    /// If directory encoding, writing, or syncing fails.
673    pub fn finish(mut self) -> Result<Table> {
674        self.flush_pending()?;
675        let mut stripes = std::mem::take(&mut self.order)
676            .into_iter()
677            .zip(std::mem::take(&mut self.table.stripes))
678            .collect::<Vec<_>>();
679        stripes.sort_by_key(|(order, _)| *order);
680        self.table.stripes = stripes.into_iter().map(|(_, stripe)| stripe).collect();
681        self.table.frequencies = self.numeric_frequencies()?;
682        for (index, dictionary) in self.dictionaries.into_iter().enumerate() {
683            let Some(dictionary) = dictionary else { continue };
684            self.table.frequencies[index] = Some(code_frequency(&dictionary));
685            let encoded = encode_global_dictionary(dictionary)?;
686            let offset = self.file.stream_position().map_err(io)?;
687            self.file.write_all(&encoded.index).map_err(io)?;
688            self.file.write_all(&encoded.payload).map_err(io)?;
689            let length = encoded
690                .index
691                .len()
692                .checked_add(encoded.payload.len())
693                .ok_or_else(|| invalid("dictionary page length overflow"))?;
694            self.table.dictionaries[index] = Some(Page {
695                offset,
696                length: u32::try_from(length)
697                    .map_err(|_| invalid("dictionary page length overflow"))?,
698                hash: checksum(&encoded.index),
699            });
700        }
701        let directory = encode_directory(&self.table)?;
702        if directory.len() > MAX_DIRECTORY {
703            return Err(invalid("directory exceeds the configured bound"));
704        }
705        let offset = self.file.stream_position().map_err(io)?;
706        self.file.write_all(&directory).map_err(io)?;
707        self.file.sync_all().map_err(io)?;
708        let slot = Slot {
709            offset,
710            length: u32::try_from(directory.len())
711                .map_err(|_| invalid("directory length overflow"))?,
712            generation: self.generation,
713            hash: checksum(&directory),
714        };
715        self.file.seek(SeekFrom::Start(16)).map_err(io)?;
716        self.file.write_all(&slot.bytes()).map_err(io)?;
717        self.file.sync_all().map_err(io)?;
718        Ok(self.table)
719    }
720}
721
722/// Reads committed native column pages without holding the table in memory.
723#[derive(Debug, Clone)]
724pub struct Reader {
725    file: Arc<File>,
726    table: Arc<Table>,
727    dictionaries: Arc<Vec<OnceLock<Arc<Vector>>>>,
728    extents: Arc<Vec<Vec<ExtentPart>>>,
729    extent_cache: Arc<Vec<Mutex<Vec<CachedExtent>>>>,
730}
731
732#[derive(Debug, Clone, Copy, Default)]
733struct ExtentPart {
734    offset: u64,
735    length: usize,
736    page_start: usize,
737}
738
739#[derive(Debug)]
740struct CachedExtent {
741    offset: u64,
742    bytes: Arc<Vec<u8>>,
743}
744
745const CACHED_EXTENTS_PER_COLUMN: usize = 8;
746
747type CrossingCache = OnceLock<Box<[OnceLock<Result<Vec<u8>>>]>>;
748
749#[derive(Debug)]
750struct NativeText {
751    file: Arc<File>,
752    offsets: Vec<u32>,
753    payload: u64,
754    payload_len: usize,
755    hashes: Vec<u64>,
756    payload_blocks: Vec<OnceLock<Result<Vec<u8>>>>,
757    crossing: Vec<CrossingCache>,
758}
759
760const TEXT_PAYLOAD_BLOCK: usize = 64 * 1024;
761const TEXT_CROSSING_BLOCK: usize = 1024;
762
763impl NativeText {
764    fn payload_block(&self, block: usize) -> Result<Option<&[u8]>> {
765        let Some(slot) = self.payload_blocks.get(block) else { return Ok(None) };
766        slot.get_or_init(|| {
767            let start = block
768                .checked_mul(TEXT_PAYLOAD_BLOCK)
769                .ok_or_else(|| invalid("global dictionary block offset overflow"))?;
770            let len = TEXT_PAYLOAD_BLOCK.min(
771                self.payload_len
772                    .checked_sub(start)
773                    .ok_or_else(|| invalid("global dictionary block starts past payload"))?,
774            );
775            let mut bytes = vec![0; len];
776            read_at(&self.file, self.payload + start as u64, &mut bytes)?;
777            if checksum(&bytes)
778                != *self
779                    .hashes
780                    .get(block)
781                    .ok_or_else(|| invalid("global dictionary block has no checksum"))?
782            {
783                return Err(invalid("global dictionary payload checksum differs"));
784            }
785            Ok(bytes)
786        })
787        .as_ref()
788        .map(|bytes| Some(bytes.as_slice()))
789        .map_err(Clone::clone)
790    }
791}
792
793impl TextSource for NativeText {
794    fn len(&self) -> usize {
795        self.offsets.len().saturating_sub(1)
796    }
797
798    fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
799        let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
800        else {
801            return Ok(None);
802        };
803        if start == end {
804            return Ok(Some(&[]));
805        }
806        let first = start as usize / TEXT_PAYLOAD_BLOCK;
807        let last = (end as usize - 1) / TEXT_PAYLOAD_BLOCK;
808        if first == last {
809            let Some(block) = self.payload_block(first)? else { return Ok(None) };
810            let within = start as usize % TEXT_PAYLOAD_BLOCK;
811            return Ok(block.get(within..within + (end - start) as usize));
812        }
813        let Some(crossing) = self.crossing.get(index / TEXT_CROSSING_BLOCK) else {
814            return Ok(None);
815        };
816        let block = crossing.get_or_init(|| {
817            (0..TEXT_CROSSING_BLOCK).map(|_| OnceLock::new()).collect::<Vec<_>>().into_boxed_slice()
818        });
819        block[index % TEXT_CROSSING_BLOCK]
820            .get_or_init(|| {
821                let mut bytes = Vec::with_capacity((end - start) as usize);
822                for part in first..=last {
823                    let source = self
824                        .payload_block(part)?
825                        .ok_or_else(|| invalid("global dictionary block is missing"))?;
826                    let from = if part == first { start as usize % TEXT_PAYLOAD_BLOCK } else { 0 };
827                    let to = if part == last {
828                        (end as usize - 1) % TEXT_PAYLOAD_BLOCK + 1
829                    } else {
830                        source.len()
831                    };
832                    bytes.extend_from_slice(source.get(from..to).ok_or_else(|| {
833                        invalid("global dictionary value exceeds its payload block")
834                    })?);
835                }
836                Ok(bytes)
837            })
838            .as_ref()
839            .map(|bytes| Some(bytes.as_slice()))
840            .map_err(Clone::clone)
841    }
842
843    fn bytes_len_at(&self, index: usize) -> Result<Option<usize>> {
844        let (Some(&start), Some(&end)) = (self.offsets.get(index), self.offsets.get(index + 1))
845        else {
846            return Ok(None);
847        };
848        Ok(Some((end - start) as usize))
849    }
850
851    fn footprint(&self) -> usize {
852        self.offsets.capacity() * size_of::<u32>()
853            + self.payload_blocks.capacity() * size_of::<OnceLock<Result<Vec<u8>>>>()
854            + self.hashes.capacity() * size_of::<u64>()
855            + self
856                .payload_blocks
857                .iter()
858                .filter_map(OnceLock::get)
859                .filter_map(|result| result.as_ref().ok())
860                .map(Vec::capacity)
861                .sum::<usize>()
862            + self.crossing.capacity() * size_of::<CrossingCache>()
863            + self
864                .crossing
865                .iter()
866                .filter_map(OnceLock::get)
867                .map(|block| {
868                    block.len() * size_of::<OnceLock<Result<Vec<u8>>>>()
869                        + block
870                            .iter()
871                            .filter_map(OnceLock::get)
872                            .filter_map(|result| result.as_ref().ok())
873                            .map(Vec::capacity)
874                            .sum::<usize>()
875                })
876                .sum::<usize>()
877    }
878}
879
880/// Maps each logical page to the bounded contiguous read that contains it.
881fn extent_parts(table: &Table) -> Result<Vec<Vec<ExtentPart>>> {
882    let mut refs = Vec::with_capacity(table.stripes.len().saturating_mul(table.fields.len()));
883    for (stripe, entry) in table.stripes.iter().enumerate() {
884        for (column, page) in entry.pages.iter().enumerate() {
885            refs.push((page.offset, column, stripe, page.length as usize));
886        }
887    }
888    refs.sort_unstable_by_key(|entry| entry.0);
889    let mut parts = vec![vec![ExtentPart::default(); table.fields.len()]; table.stripes.len()];
890    let mut first = 0;
891    while first < refs.len() {
892        let (offset, column, _, first_len) = refs[first];
893        let mut end = offset
894            .checked_add(first_len as u64)
895            .ok_or_else(|| invalid("column extent range overflow"))?;
896        let mut last = first + 1;
897        while last < refs.len()
898            && last - first < EXTENT_STRIPES
899            && refs[last].1 == column
900            && refs[last].0 == end
901        {
902            end = end
903                .checked_add(refs[last].3 as u64)
904                .ok_or_else(|| invalid("column extent range overflow"))?;
905            last += 1;
906        }
907        let length = usize::try_from(end - offset)
908            .map_err(|_| invalid("column extent length exceeds this platform"))?;
909        for &(_, _, stripe, _) in &refs[first..last] {
910            let page = table.stripes[stripe].pages[column];
911            let page_start = usize::try_from(page.offset - offset)
912                .map_err(|_| invalid("column page offset exceeds this platform"))?;
913            parts[stripe][column] = ExtentPart { offset, length, page_start };
914        }
915        first = last;
916    }
917    Ok(parts)
918}
919
920impl Reader {
921    /// Opens the highest valid directory slot.
922    ///
923    /// # Errors
924    ///
925    /// If the file has no valid committed directory or a directory pointer is out of bounds.
926    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
927        let mut file = File::open(path).map_err(io)?;
928        let size = file.metadata().map_err(io)?.len();
929        if size < HEADER {
930            return Err(invalid("file is shorter than its header"));
931        }
932        let mut header = [0; HEADER as usize];
933        file.read_exact(&mut header).map_err(io)?;
934        let version = u32::from_le_bytes([header[8], header[9], header[10], header[11]]);
935        if !((&header[..8] == MAGIC && version == 8) || (&header[..8] == MAGIC_V7 && version == 7))
936        {
937            return Err(invalid("magic or major version is unsupported"));
938        }
939        let mut selected = None;
940        for start in [16, 16 + SLOT_BYTES] {
941            let slot = Slot::read(&header[start..start + SLOT_BYTES]);
942            if slot.generation == 0 || slot.length == 0 || slot.length as usize > MAX_DIRECTORY {
943                continue;
944            }
945            let Some(end) = slot.offset.checked_add(u64::from(slot.length)) else { continue };
946            if slot.offset < HEADER || end > size {
947                continue;
948            }
949            let mut bytes = vec![0; slot.length as usize];
950            file.seek(SeekFrom::Start(slot.offset)).map_err(io)?;
951            file.read_exact(&mut bytes).map_err(io)?;
952            if checksum(&bytes) == slot.hash
953                && selected
954                    .as_ref()
955                    .is_none_or(|(old, _): &(Slot, Vec<u8>)| old.generation < slot.generation)
956            {
957                selected = Some((slot, bytes));
958            }
959        }
960        let (_, bytes) = selected.ok_or_else(|| invalid("no committed directory slot is valid"))?;
961        let table = decode_directory(&bytes, size, version)?;
962        let extents = extent_parts(&table)?;
963        let dictionaries = (0..table.fields.len()).map(|_| OnceLock::new()).collect();
964        let extent_cache = (0..table.fields.len())
965            .map(|_| Mutex::new(Vec::with_capacity(CACHED_EXTENTS_PER_COLUMN)))
966            .collect::<Vec<_>>();
967        Ok(Self {
968            file: Arc::new(file),
969            table: Arc::new(table),
970            dictionaries: Arc::new(dictionaries),
971            extents: Arc::new(extents),
972            extent_cache: Arc::new(extent_cache),
973        })
974    }
975
976    /// The committed table directory.
977    #[must_use]
978    pub fn table(&self) -> &Table {
979        &self.table
980    }
981
982    /// Exact leading frequencies when the stored synopsis proves a count-descending prefix.
983    ///
984    /// The returned list can be longer than `top`. Keeping the stored tail lets a later TopN apply
985    /// additional ordering keys without losing a value tied with the requested boundary.
986    ///
987    /// # Errors
988    ///
989    /// If the column is outside the schema or a stored value does not fit its declared type.
990    pub fn top_frequencies(&self, column: usize, top: usize) -> Result<Option<Vec<(Value, u64)>>> {
991        let field = self
992            .table
993            .fields
994            .get(column)
995            .ok_or_else(|| invalid("frequency column index out of range"))?;
996        let Some(summary) = self.table.frequencies.get(column).and_then(Option::as_ref) else {
997            return Ok(None);
998        };
999        if top == 0 || summary.entries.len() < top {
1000            return Ok(None);
1001        }
1002        let boundary = summary.entries[top - 1].count;
1003        if boundary <= summary.omitted_max {
1004            return Ok(None);
1005        }
1006        let dictionary =
1007            if field.ty == LogicalType::Varchar { self.dictionary(column)? } else { None };
1008        let mut out = Vec::with_capacity(summary.entries.len());
1009        for entry in &summary.entries {
1010            let value = match entry.value {
1011                FrequencyValue::Null => Value::Null,
1012                FrequencyValue::Integer(value) => match field.ty {
1013                    LogicalType::TinyInt => Value::TinyInt(
1014                        i8::try_from(value)
1015                            .map_err(|_| invalid("frequency TINYINT is out of range"))?,
1016                    ),
1017                    LogicalType::UTinyInt => Value::UTinyInt(
1018                        u8::try_from(value)
1019                            .map_err(|_| invalid("frequency UTINYINT is out of range"))?,
1020                    ),
1021                    LogicalType::USmallInt => Value::USmallInt(
1022                        u16::try_from(value)
1023                            .map_err(|_| invalid("frequency USMALLINT is out of range"))?,
1024                    ),
1025                    LogicalType::UInteger => Value::UInteger(
1026                        u32::try_from(value)
1027                            .map_err(|_| invalid("frequency UINTEGER is out of range"))?,
1028                    ),
1029                    LogicalType::UBigInt => Value::UBigInt(
1030                        u64::try_from(value)
1031                            .map_err(|_| invalid("frequency UBIGINT is out of range"))?,
1032                    ),
1033                    LogicalType::SmallInt => Value::SmallInt(
1034                        i16::try_from(value)
1035                            .map_err(|_| invalid("frequency SMALLINT is out of range"))?,
1036                    ),
1037                    LogicalType::Integer => Value::Integer(
1038                        i32::try_from(value)
1039                            .map_err(|_| invalid("frequency INTEGER is out of range"))?,
1040                    ),
1041                    LogicalType::BigInt => Value::BigInt(
1042                        i64::try_from(value)
1043                            .map_err(|_| invalid("frequency BIGINT is out of range"))?,
1044                    ),
1045                    LogicalType::Date => Value::Date(
1046                        i32::try_from(value)
1047                            .map_err(|_| invalid("frequency DATE is out of range"))?,
1048                    ),
1049                    LogicalType::Timestamp => Value::Timestamp(
1050                        i64::try_from(value)
1051                            .map_err(|_| invalid("frequency TIMESTAMP is out of range"))?,
1052                    ),
1053                    _ => return Err(invalid("integer frequency belongs to another type")),
1054                },
1055                FrequencyValue::Code(code) => dictionary
1056                    .as_ref()
1057                    .ok_or_else(|| invalid("frequency code has no dictionary"))?
1058                    .try_value_at(code as usize)?,
1059            };
1060            out.push((value, entry.count));
1061        }
1062        Ok(Some(out))
1063    }
1064
1065    /// Sparse rows belonging to the bounded numeric frequency candidate set.
1066    ///
1067    /// The list is omitted when collecting it would exceed the fixed storage budget. A composite
1068    /// aggregate may accept a result over these rows only when its requested boundary is strictly
1069    /// greater than `omitted_max`.
1070    ///
1071    /// # Errors
1072    ///
1073    /// If the column is outside the schema.
1074    pub fn frequency_occurrences(&self, column: usize) -> Result<Option<FrequencyOccurrences>> {
1075        self.table
1076            .fields
1077            .get(column)
1078            .ok_or_else(|| invalid("frequency column index out of range"))?;
1079        let Some(summary) = self.table.frequencies.get(column).and_then(Option::as_ref) else {
1080            return Ok(None);
1081        };
1082        if summary.ordinals.is_empty() {
1083            return Ok(None);
1084        }
1085        Ok(Some(FrequencyOccurrences {
1086            omitted_max: summary.omitted_max,
1087            ordinals: summary.ordinals.clone(),
1088        }))
1089    }
1090
1091    fn dictionary(&self, column: usize) -> Result<Option<Arc<Vector>>> {
1092        let Some(page) = self.table.dictionaries[column] else { return Ok(None) };
1093        if let Some(dictionary) = self.dictionaries[column].get() {
1094            return Ok(Some(Arc::clone(dictionary)));
1095        }
1096        let dictionary = Arc::new(open_global_dictionary(
1097            Arc::clone(&self.file),
1098            page,
1099            &self.table.fields[column].ty,
1100        )?);
1101        let _ = self.dictionaries[column].set(Arc::clone(&dictionary));
1102        Ok(Some(self.dictionaries[column].get().map_or(dictionary, Arc::clone)))
1103    }
1104
1105    /// Reads only the named columns from one stripe.
1106    ///
1107    /// # Errors
1108    ///
1109    /// If a stripe, column, page, or checksum is invalid.
1110    pub fn read(&self, stripe: usize, columns: &[usize]) -> Result<Chunk> {
1111        self.read_impl(stripe, columns, true)
1112    }
1113
1114    /// Reads named columns from one stripe without prefetching adjacent stripe pages.
1115    ///
1116    /// This is intended for sparse row fetches after a selective TopN or filter. Sequential scans
1117    /// should use [`Self::read`] so adjacent pages share one extent read.
1118    ///
1119    /// # Errors
1120    ///
1121    /// If a stripe, column, page, or checksum is invalid.
1122    pub fn read_sparse(&self, stripe: usize, columns: &[usize]) -> Result<Chunk> {
1123        self.read_impl(stripe, columns, false)
1124    }
1125
1126    /// Whether an exact global-code membership index proves that a stripe cannot contain any of
1127    /// the sorted candidate codes.
1128    ///
1129    /// A file written before v8 has no membership index and conservatively keeps the stripe.
1130    ///
1131    /// # Errors
1132    ///
1133    /// If the stripe, column, index page, checksum, or delta stream is invalid.
1134    pub fn skips_codes(&self, stripe: usize, column: usize, candidates: &[u32]) -> Result<bool> {
1135        if candidates.is_empty() {
1136            return Ok(true);
1137        }
1138        if candidates.windows(2).any(|pair| pair[0] >= pair[1]) {
1139            return Err(Error::internal("native code candidates are not sorted and unique"));
1140        }
1141        let stripe =
1142            self.table.stripes.get(stripe).ok_or_else(|| invalid("stripe index out of range"))?;
1143        let Some(page) = stripe.memberships.get(column).copied().flatten() else {
1144            return Ok(false);
1145        };
1146        let mut bytes = vec![0; page.length as usize];
1147        read_at(&self.file, page.offset, &mut bytes)?;
1148        if checksum(&bytes) != page.hash {
1149            return Err(invalid("membership page checksum differs"));
1150        }
1151        let codes = decode_membership(&bytes)?;
1152        let mut left = 0;
1153        let mut right = 0;
1154        while left < codes.len() && right < candidates.len() {
1155            match codes[left].cmp(&candidates[right]) {
1156                Ordering::Less => left += 1,
1157                Ordering::Greater => right += 1,
1158                Ordering::Equal => return Ok(false),
1159            }
1160        }
1161        Ok(true)
1162    }
1163
1164    fn read_impl(&self, stripe: usize, columns: &[usize], prefetch: bool) -> Result<Chunk> {
1165        let stripe_index = stripe;
1166        let stripe =
1167            self.table.stripes.get(stripe).ok_or_else(|| invalid("stripe index out of range"))?;
1168        let mut picked = Vec::with_capacity(columns.len());
1169        for &column in columns {
1170            let field = self
1171                .table
1172                .fields
1173                .get(column)
1174                .ok_or_else(|| invalid("column index out of range"))?;
1175            let page = stripe.pages.get(column).ok_or_else(|| invalid("stripe page is missing"))?;
1176            let part = self
1177                .extents
1178                .get(stripe_index)
1179                .and_then(|parts| parts.get(column))
1180                .ok_or_else(|| invalid("column extent is missing"))?;
1181            let bytes = if !prefetch || part.length == page.length as usize {
1182                let mut bytes = vec![0; page.length as usize];
1183                read_at(&self.file, page.offset, &mut bytes)?;
1184                Arc::new(bytes)
1185            } else {
1186                let cached = self.extent_cache[column]
1187                    .lock()
1188                    .map_err(|_| invalid("column extent cache is poisoned"))?
1189                    .iter()
1190                    .find(|cached| cached.offset == part.offset)
1191                    .map(|cached| Arc::clone(&cached.bytes));
1192                if let Some(bytes) = cached {
1193                    bytes
1194                } else {
1195                    let mut bytes = vec![0; part.length];
1196                    read_at(&self.file, part.offset, &mut bytes)?;
1197                    let bytes = Arc::new(bytes);
1198                    let mut cache = self.extent_cache[column]
1199                        .lock()
1200                        .map_err(|_| invalid("column extent cache is poisoned"))?;
1201                    if let Some(cached) = cache.iter().find(|cached| cached.offset == part.offset) {
1202                        Arc::clone(&cached.bytes)
1203                    } else {
1204                        if cache.len() == CACHED_EXTENTS_PER_COLUMN {
1205                            cache.remove(0);
1206                        }
1207                        cache.push(CachedExtent { offset: part.offset, bytes: Arc::clone(&bytes) });
1208                        bytes
1209                    }
1210                }
1211            };
1212            let page_start = if prefetch { part.page_start } else { 0 };
1213            let end = page_start
1214                .checked_add(page.length as usize)
1215                .ok_or_else(|| invalid("column page range overflow"))?;
1216            let page_bytes = bytes
1217                .get(page_start..end)
1218                .ok_or_else(|| invalid("column page exceeds its extent"))?;
1219            if checksum(page_bytes) != page.hash {
1220                return Err(invalid("column page checksum differs"));
1221            }
1222            let dictionary = self.dictionary(column)?;
1223            picked.push(decode(&field.ty, stripe.rows, page_bytes, dictionary)?);
1224        }
1225        Chunk::with_rows(picked, stripe.rows)
1226    }
1227
1228    /// Whether persisted bounds prove that a stripe cannot match the predicates.
1229    #[must_use]
1230    pub fn skips(&self, stripe: usize, probes: &[Probe]) -> bool {
1231        self.table.stripes.get(stripe).is_some_and(|stripe| stripe.zone.skips(probes))
1232    }
1233}
1234
1235#[cfg(unix)]
1236fn read_at(file: &File, mut offset: u64, mut bytes: &mut [u8]) -> Result<()> {
1237    use std::os::unix::fs::FileExt;
1238    while !bytes.is_empty() {
1239        let read = file.read_at(bytes, offset).map_err(io)?;
1240        if read == 0 {
1241            return Err(invalid("column page ends before its declared length"));
1242        }
1243        offset += read as u64;
1244        bytes = &mut bytes[read..];
1245    }
1246    Ok(())
1247}
1248
1249#[cfg(not(unix))]
1250fn read_at(file: &File, offset: u64, bytes: &mut [u8]) -> Result<()> {
1251    let mut file = file.try_clone().map_err(io)?;
1252    file.seek(SeekFrom::Start(offset)).map_err(io)?;
1253    file.read_exact(bytes).map_err(io)
1254}
1255
1256fn type_tag(ty: &LogicalType) -> Result<u8> {
1257    match ty {
1258        LogicalType::SmallInt => Ok(1),
1259        LogicalType::Integer => Ok(2),
1260        LogicalType::BigInt => Ok(3),
1261        LogicalType::Varchar => Ok(4),
1262        LogicalType::Date => Ok(5),
1263        LogicalType::Timestamp => Ok(6),
1264        LogicalType::Boolean => Ok(7),
1265        LogicalType::TinyInt => Ok(8),
1266        LogicalType::UTinyInt => Ok(9),
1267        LogicalType::USmallInt => Ok(10),
1268        LogicalType::UInteger => Ok(11),
1269        LogicalType::UBigInt => Ok(12),
1270        _ => Err(Error::not_implemented(format!("native storage for {ty}"))),
1271    }
1272}
1273
1274fn tag_type(tag: u8) -> Result<LogicalType> {
1275    match tag {
1276        1 => Ok(LogicalType::SmallInt),
1277        2 => Ok(LogicalType::Integer),
1278        3 => Ok(LogicalType::BigInt),
1279        4 => Ok(LogicalType::Varchar),
1280        5 => Ok(LogicalType::Date),
1281        6 => Ok(LogicalType::Timestamp),
1282        7 => Ok(LogicalType::Boolean),
1283        8 => Ok(LogicalType::TinyInt),
1284        9 => Ok(LogicalType::UTinyInt),
1285        10 => Ok(LogicalType::USmallInt),
1286        11 => Ok(LogicalType::UInteger),
1287        12 => Ok(LogicalType::UBigInt),
1288        _ => Err(invalid("column type tag is unknown")),
1289    }
1290}
1291
1292fn put_u16(out: &mut Vec<u8>, value: u16) {
1293    out.extend_from_slice(&value.to_le_bytes());
1294}
1295fn put_u32(out: &mut Vec<u8>, value: u32) {
1296    out.extend_from_slice(&value.to_le_bytes());
1297}
1298fn put_u64(out: &mut Vec<u8>, value: u64) {
1299    out.extend_from_slice(&value.to_le_bytes());
1300}
1301fn put_var_u64(out: &mut Vec<u8>, mut value: u64) {
1302    while value >= 0x80 {
1303        out.push((value as u8 & 0x7f) | 0x80);
1304        value >>= 7;
1305    }
1306    out.push(value as u8);
1307}
1308
1309fn frequency_order(left: FrequencyValue, right: FrequencyValue) -> Ordering {
1310    match (left, right) {
1311        (FrequencyValue::Null, FrequencyValue::Null) => Ordering::Equal,
1312        (FrequencyValue::Null, _) => Ordering::Less,
1313        (_, FrequencyValue::Null) => Ordering::Greater,
1314        (FrequencyValue::Integer(left), FrequencyValue::Integer(right)) => left.cmp(&right),
1315        (FrequencyValue::Code(left), FrequencyValue::Code(right)) => left.cmp(&right),
1316        (FrequencyValue::Integer(_), FrequencyValue::Code(_)) => Ordering::Less,
1317        (FrequencyValue::Code(_), FrequencyValue::Integer(_)) => Ordering::Greater,
1318    }
1319}
1320
1321fn code_frequency(dictionary: &GlobalDictionary) -> FrequencySummary {
1322    let mut entries = dictionary
1323        .counts
1324        .iter()
1325        .enumerate()
1326        .filter(|(_, count)| **count != 0)
1327        .map(|(code, &count)| FrequencyEntry { value: FrequencyValue::Code(code as u32), count })
1328        .collect::<Vec<_>>();
1329    if dictionary.nulls != 0 {
1330        entries.push(FrequencyEntry { value: FrequencyValue::Null, count: dictionary.nulls });
1331    }
1332    entries.sort_unstable_by(|left, right| {
1333        right.count.cmp(&left.count).then_with(|| frequency_order(left.value, right.value))
1334    });
1335    let omitted_max = entries.get(FREQUENCY_ENTRIES).map_or(0, |entry| entry.count);
1336    entries.truncate(FREQUENCY_ENTRIES);
1337    FrequencySummary { entries, omitted_max, ordinals: Vec::new() }
1338}
1339
1340fn encode_directory(table: &Table) -> Result<Vec<u8>> {
1341    encode_directory_version(table, 8)
1342}
1343
1344fn encode_directory_version(table: &Table, version: u32) -> Result<Vec<u8>> {
1345    let mut out = if version == 7 { DIRECTORY_V7.to_vec() } else { DIRECTORY.to_vec() };
1346    let name = table.name.as_bytes();
1347    put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("table name too long"))?);
1348    out.extend_from_slice(name);
1349    put_u16(&mut out, u16::try_from(table.fields.len()).map_err(|_| invalid("too many columns"))?);
1350    for field in &table.fields {
1351        let name = field.name.as_bytes();
1352        put_u16(&mut out, u16::try_from(name.len()).map_err(|_| invalid("column name too long"))?);
1353        out.extend_from_slice(name);
1354        out.push(type_tag(&field.ty)?);
1355        out.push(u8::from(field.not_null));
1356    }
1357    for dictionary in &table.dictionaries {
1358        match dictionary {
1359            None => out.push(0),
1360            Some(page) => {
1361                out.push(1);
1362                put_u64(&mut out, page.offset);
1363                put_u32(&mut out, page.length);
1364                put_u64(&mut out, page.hash);
1365            }
1366        }
1367    }
1368    put_u64(&mut out, u64::try_from(table.rows).map_err(|_| invalid("row count overflow"))?);
1369    put_u32(&mut out, u32::try_from(table.stripes.len()).map_err(|_| invalid("too many stripes"))?);
1370    for stripe in &table.stripes {
1371        put_u32(
1372            &mut out,
1373            u32::try_from(stripe.rows).map_err(|_| invalid("stripe row count overflow"))?,
1374        );
1375        for page in &stripe.pages {
1376            put_u64(&mut out, page.offset);
1377            put_u32(&mut out, page.length);
1378            put_u64(&mut out, page.hash);
1379        }
1380        if version >= 8 {
1381            for (field, membership) in table.fields.iter().zip(&stripe.memberships) {
1382                if field.ty != LogicalType::Varchar {
1383                    continue;
1384                }
1385                let page = membership
1386                    .ok_or_else(|| invalid("string page has no code membership index"))?;
1387                put_u64(&mut out, page.offset);
1388                put_u32(&mut out, page.length);
1389                put_u64(&mut out, page.hash);
1390            }
1391        }
1392        for range in stripe.zone.columns() {
1393            put_bound(&mut out, range.low.as_ref())?;
1394            put_bound(&mut out, range.high.as_ref())?;
1395            put_u32(
1396                &mut out,
1397                u32::try_from(range.nulls).map_err(|_| invalid("null count overflow"))?,
1398            );
1399        }
1400    }
1401    out.extend_from_slice(FREQUENCIES);
1402    put_u16(
1403        &mut out,
1404        u16::try_from(table.frequencies.len())
1405            .map_err(|_| invalid("too many frequency columns"))?,
1406    );
1407    for summary in &table.frequencies {
1408        let Some(summary) = summary else {
1409            out.push(0);
1410            continue;
1411        };
1412        out.push(1);
1413        put_u64(&mut out, summary.omitted_max);
1414        put_u32(
1415            &mut out,
1416            u32::try_from(summary.entries.len())
1417                .map_err(|_| invalid("too many frequency entries"))?,
1418        );
1419        for entry in &summary.entries {
1420            match entry.value {
1421                FrequencyValue::Null => out.push(0),
1422                FrequencyValue::Integer(value) => {
1423                    out.push(1);
1424                    out.extend_from_slice(&value.to_le_bytes());
1425                }
1426                FrequencyValue::Code(value) => {
1427                    out.push(2);
1428                    put_u32(&mut out, value);
1429                }
1430            }
1431            put_u64(&mut out, entry.count);
1432        }
1433        put_u32(
1434            &mut out,
1435            u32::try_from(summary.ordinals.len())
1436                .map_err(|_| invalid("too many frequency ordinals"))?,
1437        );
1438        let mut previous = 0_u64;
1439        for (at, &ordinal) in summary.ordinals.iter().enumerate() {
1440            let delta = if at == 0 {
1441                ordinal
1442            } else {
1443                ordinal
1444                    .checked_sub(previous)
1445                    .ok_or_else(|| invalid("frequency ordinals are not ordered"))?
1446            };
1447            if at != 0 && delta == 0 {
1448                return Err(invalid("frequency ordinals are not unique"));
1449            }
1450            put_var_u64(&mut out, delta);
1451            previous = ordinal;
1452        }
1453    }
1454    Ok(out)
1455}
1456
1457struct Cursor<'a> {
1458    bytes: &'a [u8],
1459    at: usize,
1460}
1461impl<'a> Cursor<'a> {
1462    fn take(&mut self, len: usize) -> Result<&'a [u8]> {
1463        let end = self.at.checked_add(len).ok_or_else(|| invalid("directory offset overflow"))?;
1464        let bytes =
1465            self.bytes.get(self.at..end).ok_or_else(|| invalid("directory is truncated"))?;
1466        self.at = end;
1467        Ok(bytes)
1468    }
1469    fn u8(&mut self) -> Result<u8> {
1470        Ok(self.take(1)?[0])
1471    }
1472    fn u16(&mut self) -> Result<u16> {
1473        Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("two bytes")))
1474    }
1475    fn u32(&mut self) -> Result<u32> {
1476        Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("four bytes")))
1477    }
1478    fn u64(&mut self) -> Result<u64> {
1479        Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("eight bytes")))
1480    }
1481    fn var_u64(&mut self) -> Result<u64> {
1482        let mut value = 0_u64;
1483        for shift in (0..=63).step_by(7) {
1484            let byte = self.u8()?;
1485            let part = u64::from(byte & 0x7f);
1486            if shift == 63 && part > 1 {
1487                return Err(invalid("frequency ordinal varint overflows"));
1488            }
1489            value |= part << shift;
1490            if byte & 0x80 == 0 {
1491                return Ok(value);
1492            }
1493        }
1494        Err(invalid("frequency ordinal varint is too long"))
1495    }
1496    fn bound(&mut self) -> Result<Option<Bound>> {
1497        Ok(match self.u8()? {
1498            0 => None,
1499            1 => Some(Bound::Int(i128::from_le_bytes(
1500                self.take(16)?.try_into().expect("sixteen bytes"),
1501            ))),
1502            2 => Some(Bound::Real(f64::from_le_bytes(
1503                self.take(8)?.try_into().expect("eight bytes"),
1504            ))),
1505            3 => {
1506                let length = self.u32()? as usize;
1507                Some(Bound::Bytes(self.take(length)?.to_vec()))
1508            }
1509            _ => return Err(invalid("bound tag differs")),
1510        })
1511    }
1512    fn text(&mut self) -> Result<String> {
1513        let len = self.u16()? as usize;
1514        String::from_utf8(self.take(len)?.to_vec()).map_err(|_| invalid("name is not UTF-8"))
1515    }
1516}
1517
1518fn decode_directory(bytes: &[u8], size: u64, version: u32) -> Result<Table> {
1519    let mut cur = Cursor { bytes, at: 0 };
1520    let expected = if version == 7 { DIRECTORY_V7 } else { DIRECTORY };
1521    if cur.take(8)? != expected {
1522        return Err(invalid("directory magic differs"));
1523    }
1524    let name = cur.text()?;
1525    let width = cur.u16()? as usize;
1526    let mut fields = Vec::with_capacity(width);
1527    for _ in 0..width {
1528        let name = cur.text()?;
1529        let ty = tag_type(cur.u8()?)?;
1530        let not_null = match cur.u8()? {
1531            0 => false,
1532            1 => true,
1533            _ => return Err(invalid("nullability flag differs")),
1534        };
1535        fields.push(Field { name, ty, not_null });
1536    }
1537    let mut dictionaries = Vec::with_capacity(width);
1538    for _ in 0..width {
1539        dictionaries.push(match cur.u8()? {
1540            0 => None,
1541            1 => {
1542                let page = Page { offset: cur.u64()?, length: cur.u32()?, hash: cur.u64()? };
1543                let end = page
1544                    .offset
1545                    .checked_add(u64::from(page.length))
1546                    .ok_or_else(|| invalid("dictionary page offset overflow"))?;
1547                // A global dictionary covers a whole column, not one bounded stripe. Its lazy
1548                // payload is intentionally allowed to grow past `MAX_PAGE`; only ordinary column
1549                // pages are capped there. `Writer::finish` has already bounded this length by the
1550                // on-disk `u32`, and the range check below keeps it inside the file.
1551                if page.offset < HEADER || end > size {
1552                    return Err(invalid("dictionary page range is outside the file"));
1553                }
1554                Some(page)
1555            }
1556            _ => return Err(invalid("dictionary page tag differs")),
1557        });
1558    }
1559    let rows = usize::try_from(cur.u64()?).map_err(|_| invalid("row count does not fit"))?;
1560    let count = cur.u32()? as usize;
1561    let mut stripes = Vec::with_capacity(count);
1562    let mut total = 0_usize;
1563    for _ in 0..count {
1564        let stripe_rows = cur.u32()? as usize;
1565        if stripe_rows == 0 {
1566            return Err(invalid("empty stripe"));
1567        }
1568        total =
1569            total.checked_add(stripe_rows).ok_or_else(|| invalid("stripe row count overflow"))?;
1570        let mut pages = Vec::with_capacity(width);
1571        for _ in 0..width {
1572            let offset = cur.u64()?;
1573            let length = cur.u32()?;
1574            let hash = cur.u64()?;
1575            let end = offset
1576                .checked_add(u64::from(length))
1577                .ok_or_else(|| invalid("page offset overflow"))?;
1578            if offset < HEADER || end > size || length as usize > MAX_PAGE {
1579                return Err(invalid("page range is outside the file"));
1580            }
1581            pages.push(Page { offset, length, hash });
1582        }
1583        let mut memberships = vec![None; width];
1584        if version >= 8 {
1585            for (column, field) in fields.iter().enumerate() {
1586                if field.ty != LogicalType::Varchar {
1587                    continue;
1588                }
1589                let page = Page { offset: cur.u64()?, length: cur.u32()?, hash: cur.u64()? };
1590                let end = page
1591                    .offset
1592                    .checked_add(u64::from(page.length))
1593                    .ok_or_else(|| invalid("membership page offset overflow"))?;
1594                if page.offset < HEADER || end > size || page.length as usize > MAX_PAGE {
1595                    return Err(invalid("membership page range is outside the file"));
1596                }
1597                memberships[column] = Some(page);
1598            }
1599        }
1600        let mut ranges = Vec::with_capacity(width);
1601        for _ in 0..width {
1602            let low = cur.bound()?;
1603            let high = cur.bound()?;
1604            let nulls = cur.u32()? as usize;
1605            if nulls > stripe_rows {
1606                return Err(invalid("null count exceeds stripe rows"));
1607            }
1608            ranges.push(Range { low, high, nulls });
1609        }
1610        stripes.push(Stripe {
1611            rows: stripe_rows,
1612            pages,
1613            memberships,
1614            zone: Zone::from_ranges(ranges),
1615        });
1616    }
1617    if total != rows {
1618        return Err(invalid("table row count differs from stripes"));
1619    }
1620    let frequencies = if cur.at == bytes.len() {
1621        vec![None; width]
1622    } else {
1623        let frequency_version = match cur.take(8)? {
1624            magic if magic == FREQUENCIES_V1 => 1,
1625            magic if magic == FREQUENCIES => 2,
1626            _ => return Err(invalid("directory extension magic differs")),
1627        };
1628        if cur.u16()? as usize != width {
1629            return Err(invalid("frequency column count differs"));
1630        }
1631        let mut frequencies = Vec::with_capacity(width);
1632        for field in &fields {
1633            let summary = match cur.u8()? {
1634                0 => None,
1635                1 => {
1636                    let omitted_max = cur.u64()?;
1637                    let count = cur.u32()? as usize;
1638                    if count > FREQUENCY_ENTRIES {
1639                        return Err(invalid("frequency entry count exceeds its bound"));
1640                    }
1641                    let mut entries = Vec::with_capacity(count);
1642                    // row at a time: directory decoding validates each persisted bounded frequency entry.
1643                    for _ in 0..count {
1644                        let value = match cur.u8()? {
1645                            0 => FrequencyValue::Null,
1646                            1 => FrequencyValue::Integer(i128::from_le_bytes(
1647                                cur.take(16)?.try_into().expect("sixteen bytes"),
1648                            )),
1649                            2 => FrequencyValue::Code(cur.u32()?),
1650                            _ => return Err(invalid("frequency value tag differs")),
1651                        };
1652                        let valid = matches!(
1653                            (&field.ty, value),
1654                            (_, FrequencyValue::Null)
1655                                | (LogicalType::Varchar, FrequencyValue::Code(_))
1656                                | (
1657                                    LogicalType::TinyInt
1658                                        | LogicalType::SmallInt
1659                                        | LogicalType::Integer
1660                                        | LogicalType::BigInt
1661                                        | LogicalType::UTinyInt
1662                                        | LogicalType::USmallInt
1663                                        | LogicalType::UInteger
1664                                        | LogicalType::UBigInt
1665                                        | LogicalType::Date
1666                                        | LogicalType::Timestamp,
1667                                    FrequencyValue::Integer(_),
1668                                )
1669                        );
1670                        if !valid {
1671                            return Err(invalid("frequency value does not match its column"));
1672                        }
1673                        let count = cur.u64()?;
1674                        if count == 0 || count > rows as u64 {
1675                            return Err(invalid("frequency count is outside the table"));
1676                        }
1677                        entries.push(FrequencyEntry { value, count });
1678                    }
1679                    if entries.windows(2).any(|pair| pair[0].count < pair[1].count) {
1680                        return Err(invalid("frequency entries are not descending"));
1681                    }
1682                    let ordinals = if frequency_version == 1 {
1683                        Vec::new()
1684                    } else {
1685                        let ordinal_count = cur.u32()? as usize;
1686                        if ordinal_count > FREQUENCY_ORDINALS || ordinal_count > rows {
1687                            return Err(invalid("frequency ordinal count exceeds its bound"));
1688                        }
1689                        let mut ordinals = Vec::with_capacity(ordinal_count);
1690                        let mut previous = 0_u64;
1691                        for at in 0..ordinal_count {
1692                            let delta = cur.var_u64()?;
1693                            if at != 0 && delta == 0 {
1694                                return Err(invalid("frequency ordinals are not increasing"));
1695                            }
1696                            let ordinal = if at == 0 {
1697                                delta
1698                            } else {
1699                                previous
1700                                    .checked_add(delta)
1701                                    .ok_or_else(|| invalid("frequency ordinal overflows"))?
1702                            };
1703                            if ordinal >= rows as u64 {
1704                                return Err(invalid("frequency ordinal is outside the table"));
1705                            }
1706                            ordinals.push(ordinal);
1707                            previous = ordinal;
1708                        }
1709                        ordinals
1710                    };
1711                    Some(FrequencySummary { entries, omitted_max, ordinals })
1712                }
1713                _ => return Err(invalid("frequency summary tag differs")),
1714            };
1715            frequencies.push(summary);
1716        }
1717        frequencies
1718    };
1719    if cur.at != bytes.len() {
1720        return Err(invalid("directory has trailing bytes"));
1721    }
1722    Ok(Table { name, fields, stripes, rows, dictionaries, frequencies })
1723}
1724
1725fn put_bound(out: &mut Vec<u8>, bound: Option<&Bound>) -> Result<()> {
1726    match bound {
1727        None => out.push(0),
1728        Some(Bound::Int(value)) => {
1729            out.push(1);
1730            out.extend_from_slice(&value.to_le_bytes());
1731        }
1732        Some(Bound::Real(value)) => {
1733            out.push(2);
1734            out.extend_from_slice(&value.to_le_bytes());
1735        }
1736        Some(Bound::Bytes(value)) => {
1737            out.push(3);
1738            put_u32(out, u32::try_from(value.len()).map_err(|_| invalid("bound length overflow"))?);
1739            out.extend_from_slice(value);
1740        }
1741    }
1742    Ok(())
1743}
1744
1745fn encode(
1746    vector: &Vector,
1747    global: Option<&mut GlobalDictionary>,
1748) -> Result<(Vec<u8>, Option<Vec<u8>>)> {
1749    let ty = vector.logical_type();
1750    // flatten: the file writer needs a uniform scalar page and does it once per loaded chunk.
1751    let flat = vector.flatten()?;
1752    let mut out = Vec::new();
1753    let mut global_codes = None;
1754    if let Some(global) = global {
1755        let mut codes = Vec::with_capacity(flat.len());
1756        for row in 0..flat.len() {
1757            let text = flat.text_at(row).unwrap_or("");
1758            let code = global.code(text)?;
1759            global.observe(code, flat.is_null_at(row))?;
1760            codes.push(code);
1761        }
1762        global_codes = Some(codes);
1763    }
1764    let membership = global_codes.as_deref().map(encode_membership);
1765    let dictionary = if global_codes.is_none() && ty == &LogicalType::Varchar {
1766        string_dictionary(&flat)?
1767    } else {
1768        None
1769    };
1770    let packed_vector = if dictionary.is_none() && global_codes.is_none() {
1771        Some(flat.bit_packed()?)
1772    } else {
1773        None
1774    };
1775    let packed = packed_vector.as_ref().and_then(Vector::packed_parts);
1776    out.push(if global_codes.is_some() {
1777        3
1778    } else if dictionary.is_some() {
1779        1
1780    } else if packed.is_some() {
1781        2
1782    } else {
1783        0
1784    });
1785    let nulls = flat.validity();
1786    let flag = match nulls {
1787        Validity::AllValid => 0,
1788        Validity::AllInvalid => 1,
1789        Validity::Mask(_) => 2,
1790    };
1791    out.push(flag);
1792    if flag == 2 {
1793        for group in (0..vector.len()).step_by(8) {
1794            let mut bits = 0_u8;
1795            for bit in 0..8 {
1796                if group + bit < vector.len() && !flat.is_null_at(group + bit) {
1797                    bits |= 1 << bit;
1798                }
1799            }
1800            out.push(bits);
1801        }
1802    }
1803    if let Some(codes) = global_codes {
1804        for code in codes {
1805            put_u32(&mut out, code);
1806        }
1807        return Ok((out, membership));
1808    }
1809    if let Some(dictionary) = dictionary {
1810        out.extend_from_slice(&dictionary);
1811        return Ok((out, membership));
1812    }
1813    if let Some(packed) = packed {
1814        if packed.offset() != 0 {
1815            return Err(invalid("writer received a sliced packed vector"));
1816        }
1817        out.push(u8::try_from(packed.width()).map_err(|_| invalid("packed width overflow"))?);
1818        out.extend_from_slice(&packed.base().to_le_bytes());
1819        put_u32(
1820            &mut out,
1821            u32::try_from(packed.words().len()).map_err(|_| invalid("too many packed words"))?,
1822        );
1823        for word in packed.words() {
1824            put_u64(&mut out, *word);
1825        }
1826        return Ok((out, membership));
1827    }
1828    let data = flat.data().ok_or_else(|| invalid("scalar column did not flatten"))?;
1829    match (ty, data) {
1830        (LogicalType::TinyInt, Data::Int8(values)) => {
1831            for value in &**values {
1832                out.extend_from_slice(&value.to_le_bytes());
1833            }
1834        }
1835        (LogicalType::UTinyInt, Data::UInt8(values)) => {
1836            for value in &**values {
1837                out.extend_from_slice(&value.to_le_bytes());
1838            }
1839        }
1840        (LogicalType::SmallInt, Data::Int16(values)) => {
1841            for value in &**values {
1842                out.extend_from_slice(&value.to_le_bytes());
1843            }
1844        }
1845        (LogicalType::USmallInt, Data::UInt16(values)) => {
1846            for value in &**values {
1847                out.extend_from_slice(&value.to_le_bytes());
1848            }
1849        }
1850        (LogicalType::UInteger, Data::UInt32(values)) => {
1851            for value in &**values {
1852                out.extend_from_slice(&value.to_le_bytes());
1853            }
1854        }
1855        (LogicalType::UBigInt, Data::UInt64(values)) => {
1856            for value in &**values {
1857                out.extend_from_slice(&value.to_le_bytes());
1858            }
1859        }
1860        (LogicalType::Integer | LogicalType::Date, Data::Int32(values)) => {
1861            for value in &**values {
1862                out.extend_from_slice(&value.to_le_bytes());
1863            }
1864        }
1865        (LogicalType::BigInt | LogicalType::Timestamp, Data::Int64(values)) => {
1866            for value in &**values {
1867                out.extend_from_slice(&value.to_le_bytes());
1868            }
1869        }
1870        (LogicalType::Boolean, Data::Bool(values)) => {
1871            for value in &**values {
1872                out.push(u8::from(*value));
1873            }
1874        }
1875        (LogicalType::Varchar, Data::Varlen(values)) => {
1876            let mut bytes = Vec::new();
1877            put_u32(&mut out, 0);
1878            for row in 0..vector.len() {
1879                let value = values.bytes(row).ok_or_else(|| invalid("string view is invalid"))?;
1880                bytes.extend_from_slice(value);
1881                put_u32(
1882                    &mut out,
1883                    u32::try_from(bytes.len())
1884                        .map_err(|_| invalid("string payload exceeds 4GiB"))?,
1885                );
1886            }
1887            out.extend_from_slice(&bytes);
1888        }
1889        _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
1890    }
1891    Ok((out, membership))
1892}
1893
1894fn put_varint(out: &mut Vec<u8>, mut value: u32) {
1895    while value >= 0x80 {
1896        out.push((value as u8 & 0x7f) | 0x80);
1897        value >>= 7;
1898    }
1899    out.push(value as u8);
1900}
1901
1902fn encode_membership(codes: &[u32]) -> Vec<u8> {
1903    let mut unique = codes.to_vec();
1904    unique.sort_unstable();
1905    unique.dedup();
1906    let mut out = Vec::with_capacity(unique.len().saturating_mul(2).saturating_add(5));
1907    put_varint(&mut out, u32::try_from(unique.len()).unwrap_or(u32::MAX));
1908    let mut previous = 0;
1909    for (at, code) in unique.into_iter().enumerate() {
1910        put_varint(&mut out, if at == 0 { code } else { code - previous });
1911        previous = code;
1912    }
1913    out
1914}
1915
1916fn take_varint(bytes: &[u8], at: &mut usize) -> Result<u32> {
1917    let mut value = 0_u32;
1918    for shift in (0..35).step_by(7) {
1919        let byte = *bytes.get(*at).ok_or_else(|| invalid("membership varint is truncated"))?;
1920        *at += 1;
1921        let part = u32::from(byte & 0x7f);
1922        if shift == 28 && part > 0x0f {
1923            return Err(invalid("membership varint overflow"));
1924        }
1925        value = value
1926            .checked_add(
1927                part.checked_shl(shift).ok_or_else(|| invalid("membership varint overflow"))?,
1928            )
1929            .ok_or_else(|| invalid("membership varint overflow"))?;
1930        if byte & 0x80 == 0 {
1931            return Ok(value);
1932        }
1933    }
1934    Err(invalid("membership varint is too long"))
1935}
1936
1937fn decode_membership(bytes: &[u8]) -> Result<Vec<u32>> {
1938    let mut at = 0;
1939    let count = take_varint(bytes, &mut at)? as usize;
1940    let mut codes = Vec::with_capacity(count);
1941    let mut previous = 0_u32;
1942    for index in 0..count {
1943        let delta = take_varint(bytes, &mut at)?;
1944        let code = if index == 0 {
1945            delta
1946        } else {
1947            previous.checked_add(delta).ok_or_else(|| invalid("membership code overflow"))?
1948        };
1949        if index > 0 && code <= previous {
1950            return Err(invalid("membership codes are not increasing"));
1951        }
1952        codes.push(code);
1953        previous = code;
1954    }
1955    if at != bytes.len() {
1956        return Err(invalid("membership page has trailing bytes"));
1957    }
1958    Ok(codes)
1959}
1960
1961fn string_dictionary(vector: &Vector) -> Result<Option<Vec<u8>>> {
1962    let mut by_text = HashMap::new();
1963    let mut values = Vec::new();
1964    let mut codes = Vec::with_capacity(vector.len());
1965    let mut plain_bytes = 0_usize;
1966    for row in 0..vector.len() {
1967        let text = vector.text_at(row).unwrap_or("");
1968        plain_bytes = plain_bytes.saturating_add(text.len());
1969        let code = match by_text.get(text) {
1970            Some(&code) => code,
1971            None => {
1972                let code = u32::try_from(values.len())
1973                    .map_err(|_| invalid("too many dictionary values"))?;
1974                by_text.insert(text, code);
1975                values.push(text);
1976                code
1977            }
1978        };
1979        codes.push(code);
1980    }
1981    let dictionary_bytes = values.iter().map(|value| value.len()).sum::<usize>();
1982    let encoded = 8_usize
1983        .saturating_add((values.len() + 1).saturating_mul(4))
1984        .saturating_add(dictionary_bytes)
1985        .saturating_add(codes.len().saturating_mul(4));
1986    let plain = (vector.len() + 1).saturating_mul(4).saturating_add(plain_bytes);
1987    if encoded >= plain {
1988        return Ok(None);
1989    }
1990    let mut out = Vec::with_capacity(encoded);
1991    put_u32(
1992        &mut out,
1993        u32::try_from(values.len()).map_err(|_| invalid("too many dictionary values"))?,
1994    );
1995    put_u32(
1996        &mut out,
1997        u32::try_from(dictionary_bytes).map_err(|_| invalid("dictionary payload exceeds 4GiB"))?,
1998    );
1999    let mut offset = 0_u32;
2000    put_u32(&mut out, offset);
2001    for value in &values {
2002        offset = offset
2003            .checked_add(
2004                u32::try_from(value.len()).map_err(|_| invalid("dictionary value is too long"))?,
2005            )
2006            .ok_or_else(|| invalid("dictionary payload exceeds 4GiB"))?;
2007        put_u32(&mut out, offset);
2008    }
2009    for value in values {
2010        out.extend_from_slice(value.as_bytes());
2011    }
2012    for code in codes {
2013        put_u32(&mut out, code);
2014    }
2015    Ok(Some(out))
2016}
2017
2018struct EncodedDictionary {
2019    index: Vec<u8>,
2020    payload: Vec<u8>,
2021}
2022
2023fn encode_global_dictionary(dictionary: GlobalDictionary) -> Result<EncodedDictionary> {
2024    let values = dictionary.offsets.len() - 1;
2025    let payload_len = dictionary.payload.len();
2026    let blocks = payload_len.div_ceil(TEXT_PAYLOAD_BLOCK);
2027    let mut index = Vec::with_capacity(12 + (values + 1) * 4 + blocks * 8);
2028    put_u32(
2029        &mut index,
2030        u32::try_from(values).map_err(|_| invalid("global dictionary has too many values"))?,
2031    );
2032    put_u32(&mut index, TEXT_PAYLOAD_BLOCK as u32);
2033    put_u32(
2034        &mut index,
2035        u32::try_from(blocks).map_err(|_| invalid("global dictionary has too many blocks"))?,
2036    );
2037    for offset in dictionary.offsets {
2038        put_u32(&mut index, offset);
2039    }
2040    for block in dictionary.payload.chunks(TEXT_PAYLOAD_BLOCK) {
2041        put_u64(&mut index, checksum(block));
2042    }
2043    Ok(EncodedDictionary { index, payload: dictionary.payload })
2044}
2045
2046fn open_global_dictionary(file: Arc<File>, page: Page, ty: &LogicalType) -> Result<Vector> {
2047    if ty != &LogicalType::Varchar {
2048        return Err(invalid("global dictionary belongs to a non-string column"));
2049    }
2050    let mut header = [0; 12];
2051    read_at(&file, page.offset, &mut header)?;
2052    let count = u32::from_le_bytes(header[0..4].try_into().expect("four bytes")) as usize;
2053    let block_size = u32::from_le_bytes(header[4..8].try_into().expect("four bytes")) as usize;
2054    let blocks = u32::from_le_bytes(header[8..12].try_into().expect("four bytes")) as usize;
2055    if block_size != TEXT_PAYLOAD_BLOCK {
2056        return Err(invalid("global dictionary block width differs"));
2057    }
2058    let offset_len = (count + 1)
2059        .checked_mul(4)
2060        .ok_or_else(|| invalid("global dictionary offset count overflow"))?;
2061    let hash_len =
2062        blocks.checked_mul(8).ok_or_else(|| invalid("global dictionary block count overflow"))?;
2063    let index_len = 12usize
2064        .checked_add(offset_len)
2065        .and_then(|len| len.checked_add(hash_len))
2066        .ok_or_else(|| invalid("global dictionary header overflow"))?;
2067    if index_len > page.length as usize {
2068        return Err(invalid("global dictionary offset index exceeds its page"));
2069    }
2070    let mut index = vec![0; index_len];
2071    index[..12].copy_from_slice(&header);
2072    read_at(&file, page.offset + 12, &mut index[12..])?;
2073    if checksum(&index) != page.hash {
2074        return Err(invalid("global dictionary index checksum differs"));
2075    }
2076    let offsets = index[12..12 + offset_len]
2077        .chunks_exact(4)
2078        .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2079        .collect::<Vec<_>>();
2080    let hashes = index[12 + offset_len..]
2081        .chunks_exact(8)
2082        .map(|part| u64::from_le_bytes(part.try_into().expect("eight bytes")))
2083        .collect::<Vec<_>>();
2084    let payload_len = page.length as usize - index_len;
2085    if blocks != payload_len.div_ceil(TEXT_PAYLOAD_BLOCK) {
2086        return Err(invalid("global dictionary block count differs from its payload"));
2087    }
2088    if offsets.first() != Some(&0)
2089        || offsets.last().copied().map(|last| last as usize) != Some(payload_len)
2090        || offsets.windows(2).any(|pair| pair[0] > pair[1])
2091    {
2092        return Err(invalid("global dictionary offsets do not bound the payload"));
2093    }
2094    let payload_blocks =
2095        (0..payload_len.div_ceil(TEXT_PAYLOAD_BLOCK)).map(|_| OnceLock::new()).collect();
2096    let crossing = (0..count.div_ceil(TEXT_CROSSING_BLOCK)).map(|_| OnceLock::new()).collect();
2097    Vector::external_text(
2098        LogicalType::Varchar,
2099        Arc::new(NativeText {
2100            file,
2101            offsets,
2102            payload: page.offset + index_len as u64,
2103            payload_len,
2104            hashes,
2105            payload_blocks,
2106            crossing,
2107        }),
2108    )
2109}
2110
2111fn decode(
2112    ty: &LogicalType,
2113    rows: usize,
2114    bytes: &[u8],
2115    global: Option<Arc<Vector>>,
2116) -> Result<Vector> {
2117    let mut cur = Cursor { bytes, at: 0 };
2118    let codec = cur.u8()?;
2119    let flag = cur.u8()?;
2120    let validity = match flag {
2121        0 => Validity::AllValid,
2122        1 => Validity::AllInvalid,
2123        2 => {
2124            let mask = cur.take(rows.div_ceil(8))?;
2125            Validity::from_iter(rows, |row| mask[row / 8] >> (row % 8) & 1 == 1)
2126        }
2127        _ => return Err(invalid("page validity tag differs")),
2128    };
2129    if codec == 1 {
2130        if ty != &LogicalType::Varchar {
2131            return Err(invalid("dictionary codec belongs to a non-string page"));
2132        }
2133        let count = cur.u32()? as usize;
2134        let payload_len = cur.u32()? as usize;
2135        let offset_bytes = cur.take(
2136            (count + 1)
2137                .checked_mul(4)
2138                .ok_or_else(|| invalid("dictionary offset count overflow"))?,
2139        )?;
2140        let offsets = offset_bytes
2141            .chunks_exact(4)
2142            .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2143            .collect::<Vec<_>>();
2144        let payload = cur.take(payload_len)?.to_vec();
2145        if offsets.first() != Some(&0)
2146            || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
2147            || offsets.windows(2).any(|pair| pair[0] > pair[1])
2148        {
2149            return Err(invalid("dictionary offsets do not bound the payload"));
2150        }
2151        let mut strings = StringColumn::over(Buffer::from_vec(payload));
2152        for pair in offsets.windows(2) {
2153            strings.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
2154        }
2155        let mut codes = Vec::with_capacity(rows);
2156        for _ in 0..rows {
2157            codes.push(cur.u32()?);
2158        }
2159        if codes.iter().any(|code| *code as usize >= count) {
2160            return Err(invalid("dictionary code is out of range"));
2161        }
2162        if cur.at != bytes.len() {
2163            return Err(invalid("dictionary page has trailing bytes"));
2164        }
2165        let dictionary = Vector::flat(LogicalType::Varchar, Data::Varlen(strings))?;
2166        return Ok(Vector::dictionary(codes, dictionary)?.with_validity(validity));
2167    }
2168    if codec == 3 {
2169        let dictionary = global.ok_or_else(|| invalid("global code page has no dictionary"))?;
2170        let mut codes = Vec::with_capacity(rows);
2171        let mut highest = None;
2172        for _ in 0..rows {
2173            let code = cur.u32()?;
2174            highest = Some(highest.map_or(code, |old: u32| old.max(code)));
2175            codes.push(code);
2176        }
2177        if cur.at != bytes.len() {
2178            return Err(invalid("global code page has trailing bytes"));
2179        }
2180        return Ok(Vector::stable_dictionary_validated(codes, dictionary, highest)?
2181            .with_validity(validity));
2182    }
2183    if codec == 2 {
2184        let width = u32::from(cur.u8()?);
2185        let base = i128::from_le_bytes(cur.take(16)?.try_into().expect("sixteen bytes"));
2186        let count = cur.u32()? as usize;
2187        let mut words = Vec::with_capacity(count);
2188        for _ in 0..count {
2189            words.push(cur.u64()?);
2190        }
2191        if cur.at != bytes.len() {
2192            return Err(invalid("packed page has trailing bytes"));
2193        }
2194        return Ok(Vector::packed(ty.clone(), words, width, base, rows)?.with_validity(validity));
2195    }
2196    if codec != 0 {
2197        return Err(invalid("page codec is unknown"));
2198    }
2199    let data = match ty {
2200        LogicalType::TinyInt => {
2201            let values = cur.take(rows)?;
2202            Data::Int8(values.iter().map(|item| *item as i8).collect::<Vec<_>>().into())
2203        }
2204        LogicalType::UTinyInt => Data::UInt8(cur.take(rows)?.to_vec().into()),
2205        LogicalType::SmallInt => {
2206            let values =
2207                cur.take(rows.checked_mul(2).ok_or_else(|| invalid("page size overflow"))?)?;
2208            Data::Int16(
2209                values
2210                    .chunks_exact(2)
2211                    .map(|item| i16::from_le_bytes(item.try_into().expect("two bytes")))
2212                    .collect::<Vec<_>>()
2213                    .into(),
2214            )
2215        }
2216        LogicalType::USmallInt => {
2217            let values =
2218                cur.take(rows.checked_mul(2).ok_or_else(|| invalid("page size overflow"))?)?;
2219            Data::UInt16(
2220                values
2221                    .chunks_exact(2)
2222                    .map(|item| u16::from_le_bytes(item.try_into().expect("two bytes")))
2223                    .collect::<Vec<_>>()
2224                    .into(),
2225            )
2226        }
2227        LogicalType::UInteger => {
2228            let values =
2229                cur.take(rows.checked_mul(4).ok_or_else(|| invalid("page size overflow"))?)?;
2230            Data::UInt32(
2231                values
2232                    .chunks_exact(4)
2233                    .map(|item| u32::from_le_bytes(item.try_into().expect("four bytes")))
2234                    .collect::<Vec<_>>()
2235                    .into(),
2236            )
2237        }
2238        LogicalType::UBigInt => {
2239            let values =
2240                cur.take(rows.checked_mul(8).ok_or_else(|| invalid("page size overflow"))?)?;
2241            Data::UInt64(
2242                values
2243                    .chunks_exact(8)
2244                    .map(|item| u64::from_le_bytes(item.try_into().expect("eight bytes")))
2245                    .collect::<Vec<_>>()
2246                    .into(),
2247            )
2248        }
2249        LogicalType::Integer | LogicalType::Date => {
2250            let values =
2251                cur.take(rows.checked_mul(4).ok_or_else(|| invalid("page size overflow"))?)?;
2252            Data::Int32(
2253                values
2254                    .chunks_exact(4)
2255                    .map(|item| i32::from_le_bytes(item.try_into().expect("four bytes")))
2256                    .collect::<Vec<_>>()
2257                    .into(),
2258            )
2259        }
2260        LogicalType::BigInt | LogicalType::Timestamp => {
2261            let values =
2262                cur.take(rows.checked_mul(8).ok_or_else(|| invalid("page size overflow"))?)?;
2263            Data::Int64(
2264                values
2265                    .chunks_exact(8)
2266                    .map(|item| i64::from_le_bytes(item.try_into().expect("eight bytes")))
2267                    .collect::<Vec<_>>()
2268                    .into(),
2269            )
2270        }
2271        LogicalType::Boolean => {
2272            let values = cur.take(rows)?;
2273            if values.iter().any(|value| *value > 1) {
2274                return Err(invalid("boolean page has another value"));
2275            }
2276            Data::Bool(values.iter().map(|value| *value == 1).collect::<Vec<_>>().into())
2277        }
2278        LogicalType::Varchar => {
2279            let offset_bytes = cur
2280                .take((rows + 1).checked_mul(4).ok_or_else(|| invalid("offset count overflow"))?)?;
2281            let offsets = offset_bytes
2282                .chunks_exact(4)
2283                .map(|part| u32::from_le_bytes(part.try_into().expect("four bytes")))
2284                .collect::<Vec<_>>();
2285            let payload = cur.take(bytes.len() - cur.at)?.to_vec();
2286            if offsets.first() != Some(&0)
2287                || offsets.last().copied().map(|last| last as usize) != Some(payload.len())
2288                || offsets.windows(2).any(|pair| pair[0] > pair[1])
2289            {
2290                return Err(invalid("string offsets do not bound the payload"));
2291            }
2292            let mut values = StringColumn::over(Buffer::from_vec(payload));
2293            for pair in offsets.windows(2) {
2294                values.push_in_place(pair[0] as usize, (pair[1] - pair[0]) as usize)?;
2295            }
2296            Data::Varlen(values)
2297        }
2298        _ => return Err(Error::not_implemented(format!("native page for {ty}"))),
2299    };
2300    if cur.at != bytes.len() {
2301        return Err(invalid("page has trailing bytes"));
2302    }
2303    Ok(Vector::flat(ty.clone(), data)?.with_validity(validity))
2304}
2305
2306#[cfg(test)]
2307mod tests {
2308    use std::fs;
2309    use std::io::{Seek, SeekFrom, Write};
2310    use std::path::PathBuf;
2311    use std::time::{SystemTime, UNIX_EPOCH};
2312
2313    use rudb_common::Value;
2314    use rudb_common::bounds::Op;
2315
2316    use super::*;
2317
2318    #[test]
2319    fn checksum_matches_fixed_vectors() {
2320        assert_eq!(checksum(b""), 0xef46_db37_51d8_e999);
2321        assert_eq!(checksum(b"a"), 0xd24e_c4f1_a98c_6e5b);
2322        assert_eq!(checksum(b"abc"), 0x44bc_2cf5_ad77_0999);
2323    }
2324
2325    fn path(label: &str) -> PathBuf {
2326        let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
2327        std::env::temp_dir().join(format!("rudb-native-{label}-{}-{stamp}.rdb", std::process::id()))
2328    }
2329
2330    fn sample() -> Chunk {
2331        Chunk::new(vec![
2332            Vector::from_values(
2333                LogicalType::Integer,
2334                &[Value::Integer(4), Value::Integer(9), Value::Integer(-2)],
2335            )
2336            .expect("integers"),
2337            Vector::from_values(
2338                LogicalType::Varchar,
2339                &[
2340                    Value::Varchar("alpha".into()),
2341                    Value::Null,
2342                    Value::Varchar("long text after a slash".into()),
2343                ],
2344            )
2345            .expect("strings"),
2346        ])
2347        .expect("matching rows")
2348    }
2349
2350    #[test]
2351    fn committed_file_reopens_and_reads_only_requested_columns() {
2352        let path = path("reopen");
2353        let mut writer = Writer::create(
2354            &path,
2355            "items",
2356            vec![
2357                Field::required("id", LogicalType::Integer),
2358                Field::new("text", LogicalType::Varchar),
2359            ],
2360        )
2361        .expect("new file");
2362        writer.append(&sample()).expect("first stripe");
2363        writer.append(&sample()).expect("second stripe");
2364        writer.finish().expect("commit");
2365        let reader = Reader::open(&path).expect("reopen from disk");
2366        assert_eq!(reader.table().rows(), 6);
2367        assert_eq!(reader.table().stripes().len(), 2);
2368        let text = reader.read(1, &[1]).expect("only text page");
2369        assert_eq!(text.width(), 1);
2370        assert_eq!(text.value_at(1, 0), Value::Null);
2371        assert_eq!(text.value_at(2, 0), Value::Varchar("long text after a slash".into()));
2372        let sparse = reader.read_sparse(1, &[1]).expect("one page without extent prefetch");
2373        assert_eq!(sparse.width(), 1);
2374        assert_eq!(sparse.value_at(1, 0), Value::Null);
2375        assert_eq!(sparse.value_at(2, 0), Value::Varchar("long text after a slash".into()));
2376        assert!(!reader.skips_codes(0, 1, &[0]).expect("alpha is in the stripe"));
2377        assert!(!reader.skips_codes(0, 1, &[2]).expect("long text is in the stripe"));
2378        assert!(reader.skips_codes(0, 1, &[3]).expect("unknown code is absent"));
2379        let count = reader.read(0, &[]).expect("no page is needed for count");
2380        assert_eq!(count.len(), 3);
2381        assert!(reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(100) }]));
2382        assert!(!reader.skips(0, &[Probe { column: 0, op: Op::Greater, value: Bound::Int(0) }]));
2383        let integers = reader.top_frequencies(0, 1).expect("valid integer synopsis").expect("kept");
2384        assert_eq!(
2385            integers,
2386            vec![(Value::Integer(-2), 2), (Value::Integer(4), 2), (Value::Integer(9), 2),]
2387        );
2388        let strings = reader.top_frequencies(1, 1).expect("valid string synopsis").expect("kept");
2389        assert_eq!(strings.len(), 3);
2390        assert!(strings.contains(&(Value::Null, 2)));
2391        assert!(strings.contains(&(Value::Varchar("alpha".into()), 2)));
2392        assert!(strings.contains(&(Value::Varchar("long text after a slash".into()), 2)));
2393        fs::remove_file(path).expect("remove scratch file");
2394    }
2395
2396    /// Every integer width the format knows about, written and read back.
2397    ///
2398    /// The unsigned ones are the reason ClickBench can be stored at all: `hits` types `EventDate`
2399    /// as `USMALLINT`, and one unsupported column meant the whole table was refused. The extremes
2400    /// are in here on purpose, because a width that round trips through the wrong signedness only
2401    /// goes wrong at the end of its range.
2402    #[test]
2403    fn every_integer_width_round_trips_through_a_page() {
2404        let path = path("integer-widths");
2405        let columns = [
2406            (LogicalType::TinyInt, vec![Value::TinyInt(i8::MIN), Value::TinyInt(i8::MAX)]),
2407            (LogicalType::UTinyInt, vec![Value::UTinyInt(0), Value::UTinyInt(u8::MAX)]),
2408            (LogicalType::SmallInt, vec![Value::SmallInt(i16::MIN), Value::SmallInt(i16::MAX)]),
2409            (LogicalType::USmallInt, vec![Value::USmallInt(0), Value::USmallInt(u16::MAX)]),
2410            (LogicalType::Integer, vec![Value::Integer(i32::MIN), Value::Integer(i32::MAX)]),
2411            (LogicalType::UInteger, vec![Value::UInteger(0), Value::UInteger(u32::MAX)]),
2412            (LogicalType::BigInt, vec![Value::BigInt(i64::MIN), Value::BigInt(i64::MAX)]),
2413            (LogicalType::UBigInt, vec![Value::UBigInt(0), Value::UBigInt(u64::MAX)]),
2414        ];
2415        let fields = columns
2416            .iter()
2417            .enumerate()
2418            .map(|(at, (ty, _))| Field::required(format!("c{at}"), ty.clone()))
2419            .collect::<Vec<_>>();
2420        let vectors = columns
2421            .iter()
2422            .map(|(ty, values)| Vector::from_values(ty.clone(), values).expect("a vector"))
2423            .collect::<Vec<_>>();
2424        let mut writer = Writer::create(&path, "widths", fields).expect("new file");
2425        writer.append(&Chunk::new(vectors).expect("matching rows")).expect("one stripe");
2426        writer.finish().expect("commit");
2427
2428        let reader = Reader::open(&path).expect("reopen from disk");
2429        let wanted = (0..columns.len()).collect::<Vec<_>>();
2430        let read = reader.read(0, &wanted).expect("every column");
2431        assert_eq!(read.len(), 2);
2432        // row at a time: each column has its own type and its own pair of extremes.
2433        for (at, (ty, values)) in columns.iter().enumerate() {
2434            assert_eq!(read.value_at(0, at), values[0], "the low end of {ty}");
2435            assert_eq!(read.value_at(1, at), values[1], "the high end of {ty}");
2436        }
2437        fs::remove_file(path).expect("remove scratch file");
2438    }
2439
2440    #[test]
2441    fn numeric_frequency_candidates_keep_bounded_row_ordinals() {
2442        let path = path("frequency-ordinals");
2443        let mut writer =
2444            Writer::create(&path, "items", vec![Field::required("id", LogicalType::BigInt)])
2445                .expect("new file");
2446        let mut values = Vec::new();
2447        for leader in 0..10_i64 {
2448            values.extend(std::iter::repeat_n(leader, 100));
2449        }
2450        values.extend(1_000_i64..41_000);
2451        for part in values.chunks(1_024) {
2452            let vector = Vector::flat(LogicalType::BigInt, Data::Int64(part.to_vec().into()))
2453                .expect("big integers");
2454            writer.append(&Chunk::new(vec![vector]).expect("one column")).expect("one stripe");
2455        }
2456        writer.finish().expect("commit");
2457
2458        let reader = Reader::open(&path).expect("reopen from disk");
2459        let occurrences =
2460            reader.frequency_occurrences(0).expect("valid metadata").expect("bounded ordinals");
2461        assert!(occurrences.omitted_max < 100);
2462        assert!(occurrences.ordinals.len() <= FREQUENCY_ORDINALS);
2463        assert!(occurrences.ordinals.windows(2).all(|pair| pair[0] < pair[1]));
2464        assert_eq!(&occurrences.ordinals[..1_000], &(0_u64..1_000).collect::<Vec<_>>());
2465        fs::remove_file(path).expect("remove scratch file");
2466    }
2467
2468    #[test]
2469    fn an_unfinished_or_damaged_file_does_not_answer_with_partial_rows() {
2470        let unfinished = path("unfinished");
2471        let mut writer =
2472            Writer::create(&unfinished, "items", vec![Field::new("id", LogicalType::Integer)])
2473                .expect("new file");
2474        let chunk = Chunk::new(vec![
2475            Vector::flat(LogicalType::Integer, Data::Int32(vec![1, 2, 3].into()))
2476                .expect("integers"),
2477        ])
2478        .expect("chunk");
2479        writer.append(&chunk).expect("page written");
2480        drop(writer);
2481        assert!(Reader::open(&unfinished).is_err(), "no directory was committed");
2482        fs::remove_file(unfinished).expect("remove scratch file");
2483
2484        let damaged = path("damaged");
2485        let mut writer =
2486            Writer::create(&damaged, "items", vec![Field::new("id", LogicalType::Integer)])
2487                .expect("new file");
2488        writer.append(&chunk).expect("page written");
2489        writer.finish().expect("commit");
2490        let reader = Reader::open(&damaged).expect("valid directory");
2491        let mut file =
2492            OpenOptions::new().write(true).open(&damaged).expect("open for a damaged page");
2493        file.seek(SeekFrom::Start(HEADER + 1)).expect("inside first page");
2494        file.write_all(&[255]).expect("damage one byte");
2495        assert!(reader.read(0, &[0]).is_err(), "page checksum rejects corruption");
2496        fs::remove_file(damaged).expect("remove scratch file");
2497    }
2498
2499    #[test]
2500    fn damaged_lazy_dictionary_payload_is_an_error() {
2501        let path = path("damaged-dictionary");
2502        let mut writer = Writer::create(
2503            &path,
2504            "items",
2505            vec![
2506                Field::required("id", LogicalType::Integer),
2507                Field::new("text", LogicalType::Varchar),
2508            ],
2509        )
2510        .expect("new file");
2511        writer.append(&sample()).expect("stripe written");
2512        writer.finish().expect("commit");
2513
2514        let reader = Reader::open(&path).expect("valid directory");
2515        let dictionary = reader.table.dictionaries[1].expect("string dictionary page");
2516        let index_len = 12_u64 + 4 * 4 + 8;
2517        let mut file = OpenOptions::new().write(true).open(&path).expect("open dictionary page");
2518        file.seek(SeekFrom::Start(dictionary.offset + index_len))
2519            .expect("inside dictionary payload");
2520        file.write_all(&[255]).expect("damage dictionary payload");
2521
2522        let chunk = reader.read(0, &[1]).expect("code page and dictionary index remain valid");
2523        let error =
2524            chunk.validate_external().expect_err("payload corruption must reach the caller");
2525        assert!(error.message().contains("payload checksum differs"), "{error}");
2526        fs::remove_file(path).expect("remove scratch file");
2527    }
2528
2529    #[test]
2530    fn damaged_membership_cannot_skip_a_string_page() {
2531        let path = path("damaged-membership");
2532        let mut writer = Writer::create(
2533            &path,
2534            "items",
2535            vec![
2536                Field::required("id", LogicalType::Integer),
2537                Field::new("text", LogicalType::Varchar),
2538            ],
2539        )
2540        .expect("new file");
2541        writer.append(&sample()).expect("stripe written");
2542        writer.finish().expect("commit");
2543
2544        let reader = Reader::open(&path).expect("valid directory");
2545        let membership = reader.table.stripes[0].memberships[1].expect("string membership");
2546        let mut file = OpenOptions::new().write(true).open(&path).expect("open membership page");
2547        file.seek(SeekFrom::Start(membership.offset)).expect("membership start");
2548        file.write_all(&[255]).expect("damage membership");
2549        let error = reader.skips_codes(0, 1, &[3]).expect_err("corruption must not skip rows");
2550        assert!(error.message().contains("membership page checksum differs"), "{error}");
2551        fs::remove_file(path).expect("remove scratch file");
2552    }
2553
2554    #[test]
2555    fn membership_delta_stream_is_sorted_exact_and_bounded() {
2556        let encoded = encode_membership(&[900, 4, 4, 72, 9, u32::MAX]);
2557        assert_eq!(
2558            decode_membership(&encoded).expect("valid membership"),
2559            [4, 9, 72, 900, u32::MAX]
2560        );
2561        assert!(decode_membership(&[1, 0x80]).is_err(), "a truncated varint is invalid");
2562        assert!(
2563            decode_membership(&[1, 0xff, 0xff, 0xff, 0xff, 0x10]).is_err(),
2564            "a value past u32 is invalid"
2565        );
2566    }
2567
2568    #[test]
2569    fn a_global_dictionary_may_be_larger_than_one_column_page() {
2570        let dictionary = Page {
2571            offset: HEADER,
2572            length: u32::try_from(MAX_PAGE + 1).expect("the page bound fits on disk"),
2573            hash: 0,
2574        };
2575        let table = Table {
2576            name: "items".to_owned(),
2577            fields: vec![Field::new("text", LogicalType::Varchar)],
2578            stripes: Vec::new(),
2579            rows: 0,
2580            dictionaries: vec![Some(dictionary)],
2581            frequencies: vec![None],
2582        };
2583        let directory = encode_directory(&table).expect("directory");
2584        let file_size = dictionary.offset + u64::from(dictionary.length) + 1;
2585
2586        let decoded = decode_directory(&directory, file_size, 8).expect("large lazy dictionary");
2587        assert_eq!(decoded.dictionaries[0].expect("dictionary").length, dictionary.length);
2588        let legacy = encode_directory_version(&table, 7).expect("legacy directory");
2589        let decoded = decode_directory(&legacy, file_size, 7).expect("v7 remains readable");
2590        assert_eq!(decoded.dictionaries[0].expect("legacy dictionary").length, dictionary.length);
2591    }
2592}