Skip to main content

ic_sqlite_vfs/db/
mod.rs

1//! Public SQLite database facade for canister methods.
2//!
3//! Update paths accept only synchronous closures, which prevents holding a DB
4//! transaction across `await`. Query paths open SQLite in read-only/query-only mode.
5
6pub mod connection;
7pub mod migrate;
8pub mod pragmas;
9pub mod row;
10pub mod statement;
11pub mod transaction;
12pub mod value;
13
14use crate::sqlite_vfs::stable_blob;
15use crate::stable::meta::Superblock;
16use connection::Connection;
17pub use row::{FromColumn, Row};
18pub use stable_blob::ChecksumRefresh;
19use std::ffi::c_int;
20pub use transaction::UpdateConnection;
21pub use value::{Null, ToSql, Value, NULL};
22
23#[derive(Debug, thiserror::Error)]
24pub enum DbError {
25    #[error("sqlite error {0}: {1}")]
26    Sqlite(c_int, String),
27    #[error("sqlite constraint failed: {0}")]
28    Constraint(String),
29    #[error("query returned no rows")]
30    NotFound,
31    #[error("column {index} has type {actual}, expected {expected}")]
32    TypeMismatch {
33        index: usize,
34        expected: &'static str,
35        actual: &'static str,
36    },
37    #[error("column index {index} out of range for {count} columns")]
38    ColumnOutOfRange { index: usize, count: usize },
39    #[error("stable memory error: {0}")]
40    Stable(#[from] crate::stable::memory::StableMemoryError),
41    #[error("migration version exceeds SQLite INTEGER range: {0}")]
42    MigrationVersionOutOfRange(u64),
43    #[error("SQL contains an interior NUL byte")]
44    InteriorNul,
45    #[error("text value too large")]
46    TextTooLarge,
47    #[error("blob value too large")]
48    BlobTooLarge,
49    #[error("too many SQL parameters")]
50    TooManyParameters,
51    #[error("SQL parameter not found: {0}")]
52    ParameterNotFound(String),
53}
54
55pub struct Db;
56
57impl Db {
58    pub fn init() -> Result<(), DbError> {
59        crate::sqlite_vfs::register();
60        Superblock::load()?;
61        Ok(())
62    }
63
64    pub fn update<T, F>(f: F) -> Result<T, DbError>
65    where
66        F: FnOnce(&mut UpdateConnection<'_>) -> Result<T, DbError>,
67    {
68        Self::init()?;
69        stable_blob::begin_update()?;
70        let _overlay_guard = OverlayGuard;
71        let connection = connection::open_read_write()?;
72        transaction::run_immediate(&connection, f)
73    }
74
75    pub fn query<T, F>(f: F) -> Result<T, DbError>
76    where
77        F: FnOnce(&Connection) -> Result<T, DbError>,
78    {
79        Self::init()?;
80        let connection = connection::open_read_only()?;
81        f(&connection)
82    }
83
84    pub fn migrate(migrations: &[migrate::Migration]) -> Result<(), DbError> {
85        Self::update(|connection| migrate::apply(connection, migrations))?;
86        let target_version = migrations
87            .iter()
88            .map(|migration| migration.version)
89            .max()
90            .unwrap_or(0);
91        let mut block = Superblock::load()?;
92        if block.schema_version < target_version {
93            block.schema_version = target_version;
94            block.store()?;
95        }
96        Ok(())
97    }
98
99    pub fn integrity_check() -> Result<String, DbError> {
100        Self::query(|connection| connection.query_string("PRAGMA integrity_check"))
101    }
102
103    pub fn export_chunk(offset: u64, len: u64) -> Result<Vec<u8>, DbError> {
104        Self::init()?;
105        stable_blob::export_chunk(offset, len).map_err(DbError::from)
106    }
107
108    pub fn db_checksum() -> Result<u64, DbError> {
109        Self::init()?;
110        stable_blob::checksum().map_err(DbError::from)
111    }
112
113    pub fn refresh_checksum() -> Result<u64, DbError> {
114        Self::init()?;
115        stable_blob::refresh_checksum().map_err(DbError::from)
116    }
117
118    pub fn refresh_checksum_chunk(max_bytes: u64) -> Result<ChecksumRefresh, DbError> {
119        Self::init()?;
120        stable_blob::refresh_checksum_chunk(max_bytes).map_err(DbError::from)
121    }
122
123    pub fn begin_import(total_size: u64, expected_checksum: u64) -> Result<(), DbError> {
124        Self::init()?;
125        stable_blob::begin_import(total_size, expected_checksum).map_err(DbError::from)
126    }
127
128    pub fn import_chunk(offset: u64, bytes: &[u8]) -> Result<(), DbError> {
129        Self::init()?;
130        stable_blob::import_chunk(offset, bytes).map_err(DbError::from)
131    }
132
133    pub fn finish_import() -> Result<(), DbError> {
134        Self::init()?;
135        stable_blob::finish_import().map_err(DbError::from)
136    }
137}
138
139struct OverlayGuard;
140
141impl Drop for OverlayGuard {
142    fn drop(&mut self) {
143        stable_blob::rollback_update();
144    }
145}