Skip to main content

uqa_storage/sqlite/compressed_vfs/
options.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Compression codec selection and validated chunk geometry.
8
9use super::{DEFAULT_CHUNK_PAGES, DEFAULT_LEVEL, DEFAULT_PAGE_SIZE};
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub enum SQLiteCompressionCodec {
13    #[default]
14    Zstd,
15    LZ4,
16}
17
18impl SQLiteCompressionCodec {
19    pub(super) const fn id(self) -> u32 {
20        match self {
21            Self::Zstd => 1,
22            Self::LZ4 => 2,
23        }
24    }
25
26    pub(super) fn from_id(id: u32) -> Result<Self, String> {
27        match id {
28            0 | 1 => Ok(Self::Zstd),
29            2 => Ok(Self::LZ4),
30            _ => Err(format!("unsupported compression codec id {id}")),
31        }
32    }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct SQLiteCompressionOptions {
37    pub codec: SQLiteCompressionCodec,
38    pub page_size: u32,
39    pub chunk_pages: u32,
40    pub level: i32,
41}
42
43impl Default for SQLiteCompressionOptions {
44    fn default() -> Self {
45        Self {
46            codec: SQLiteCompressionCodec::default(),
47            page_size: DEFAULT_PAGE_SIZE,
48            chunk_pages: DEFAULT_CHUNK_PAGES,
49            level: DEFAULT_LEVEL,
50        }
51    }
52}
53
54impl SQLiteCompressionOptions {
55    pub fn zstd() -> Self {
56        Self {
57            codec: SQLiteCompressionCodec::Zstd,
58            ..Self::default()
59        }
60    }
61
62    pub fn lz4() -> Self {
63        Self {
64            codec: SQLiteCompressionCodec::LZ4,
65            level: 0,
66            ..Self::default()
67        }
68    }
69
70    pub fn validate(self) -> Result<Self, String> {
71        if self.page_size == 0 || !self.page_size.is_power_of_two() {
72            return Err("page_size must be a non-zero power of two".to_string());
73        }
74        if !(512..=65_536).contains(&self.page_size) {
75            return Err("page_size must be between 512 and 65536 bytes".to_string());
76        }
77        if self.chunk_pages == 0 {
78            return Err("chunk_pages must be non-zero".to_string());
79        }
80        let chunk_size = u64::from(self.page_size) * u64::from(self.chunk_pages);
81        if !(u64::from(self.page_size)..=1_048_576).contains(&chunk_size) {
82            return Err("chunk size must be at most 1 MiB".to_string());
83        }
84        if self.codec == SQLiteCompressionCodec::Zstd && !(-7..=22).contains(&self.level) {
85            return Err("zstd level must be between -7 and 22".to_string());
86        }
87        Ok(self)
88    }
89
90    pub fn chunk_size(self) -> Result<usize, String> {
91        let validated = self.validate()?;
92        let page_size = usize::try_from(validated.page_size)
93            .map_err(|_| "page_size exceeds the addressable range".to_string())?;
94        let chunk_pages = usize::try_from(validated.chunk_pages)
95            .map_err(|_| "chunk_pages exceeds the addressable range".to_string())?;
96        page_size
97            .checked_mul(chunk_pages)
98            .ok_or_else(|| "chunk size exceeds the addressable range".to_string())
99    }
100}