1pub 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::memory::{self, ContextId, DbMemory};
16use crate::stable::meta::Superblock;
17use connection::Connection;
18pub use row::{FromColumn, Row, TextLen};
19pub use stable_blob::ChecksumRefresh;
20use std::cell::RefCell;
21use std::collections::BTreeMap;
22use std::ffi::c_int;
23use std::rc::Rc;
24pub use transaction::UpdateConnection;
25pub use value::{Null, ToSql, Value, NULL};
26
27thread_local! {
28 static READ_CONNECTIONS: RefCell<BTreeMap<ContextId, Rc<Connection>>> = const { RefCell::new(BTreeMap::new()) };
29 static WRITE_CONNECTIONS: RefCell<BTreeMap<ContextId, Rc<Connection>>> = const { RefCell::new(BTreeMap::new()) };
30 static ACTIVE_READ_CONNECTIONS: RefCell<Vec<(ContextId, usize)>> = const { RefCell::new(Vec::new()) };
31}
32
33#[derive(Debug, thiserror::Error)]
34pub enum DbError {
35 #[error("sqlite error {0}: {1}")]
36 Sqlite(c_int, String),
37 #[error("sqlite constraint failed: {0}")]
38 Constraint(String),
39 #[error("query returned no rows")]
40 NotFound,
41 #[error("column {index} has type {actual}, expected {expected}")]
42 TypeMismatch {
43 index: usize,
44 expected: &'static str,
45 actual: &'static str,
46 },
47 #[error("column index {index} out of range for {count} columns")]
48 ColumnOutOfRange { index: usize, count: usize },
49 #[error("stable memory error: {0}")]
50 Stable(#[from] crate::stable::memory::StableMemoryError),
51 #[error("stable memory backend is not initialized; call Db::init(memory) first")]
52 StableMemoryNotInitialized,
53 #[error("stable memory backend is already initialized")]
54 StableMemoryAlreadyInitialized,
55 #[error("cannot mutate database while a query connection is active")]
56 ReadConnectionInUse,
57 #[error("migration version exceeds SQLite INTEGER range: {0}")]
58 MigrationVersionOutOfRange(u64),
59 #[error("duplicate migration version: {0}")]
60 DuplicateMigrationVersion(u64),
61 #[error("migration versions must be strictly increasing: previous={previous}, next={next}")]
62 MigrationVersionOutOfOrder { previous: u64, next: u64 },
63 #[error("SQL contains an interior NUL byte")]
64 InteriorNul,
65 #[error("SQL contains no statement")]
66 EmptySql,
67 #[error("SQL contains trailing text after the first statement")]
68 TrailingSql,
69 #[error("text value too large")]
70 TextTooLarge,
71 #[error("blob value too large")]
72 BlobTooLarge,
73 #[error("too many SQL parameters")]
74 TooManyParameters,
75 #[error("SQL parameter count mismatch: expected {expected}, actual {actual}")]
76 ParameterCountMismatch { expected: usize, actual: usize },
77 #[error("named bind cannot be used with anonymous SQL parameter at index {index}")]
78 AnonymousParameterInNamedBind { index: usize },
79 #[error("SQL parameter not found: {0}")]
80 ParameterNotFound(String),
81}
82
83pub struct Db;
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub struct DbHandle {
87 context: ContextId,
88}
89
90impl Db {
91 pub fn init(memory: DbMemory) -> Result<(), DbError> {
92 let context = memory::init(memory).map_err(|error| match error {
93 crate::stable::memory::StableMemoryError::AlreadyInitialized => {
94 DbError::StableMemoryAlreadyInitialized
95 }
96 crate::stable::memory::StableMemoryError::NotInitialized => {
97 DbError::StableMemoryNotInitialized
98 }
99 error => DbError::Stable(error),
100 })?;
101 clear_read_connection(context);
102 clear_write_connection(context);
103 let handle = DbHandle::from_context(context);
104 let result = handle.initialize();
105 if result.is_err() {
106 clear_read_connection(context);
107 clear_write_connection(context);
108 memory::clear_failed_initialization(context);
109 }
110 result
111 }
112
113 fn default_handle() -> Result<DbHandle, DbError> {
114 memory::default_context()
115 .map(DbHandle::from_context)
116 .ok_or(DbError::StableMemoryNotInitialized)
117 }
118
119 pub fn update<T, F>(f: F) -> Result<T, DbError>
120 where
121 F: FnOnce(&mut UpdateConnection<'_>) -> Result<T, DbError>,
122 {
123 Self::default_handle()?.update(f)
124 }
125
126 pub fn query<T, F>(f: F) -> Result<T, DbError>
127 where
128 F: FnOnce(&Connection) -> Result<T, DbError>,
129 {
130 Self::default_handle()?.query(f)
131 }
132
133 pub fn migrate(migrations: &[migrate::Migration]) -> Result<(), DbError> {
134 Self::default_handle()?.migrate(migrations)
135 }
136
137 pub fn integrity_check() -> Result<String, DbError> {
138 Self::default_handle()?.integrity_check()
139 }
140
141 pub fn db_checksum() -> Result<u64, DbError> {
142 Self::default_handle()?.db_checksum()
143 }
144
145 pub fn refresh_checksum() -> Result<u64, DbError> {
146 Self::default_handle()?.refresh_checksum()
147 }
148
149 pub fn refresh_checksum_chunk(max_bytes: u64) -> Result<ChecksumRefresh, DbError> {
150 Self::default_handle()?.refresh_checksum_chunk(max_bytes)
151 }
152}
153
154impl DbHandle {
155 pub fn init(memory: DbMemory) -> Result<Self, DbError> {
156 let handle = Self::from_context(memory::init_context(memory)?);
157 clear_read_connection(handle.context);
158 clear_write_connection(handle.context);
159 if let Err(error) = handle.initialize() {
160 clear_read_connection(handle.context);
161 clear_write_connection(handle.context);
162 memory::clear_failed_initialization(handle.context);
163 return Err(error);
164 }
165 Ok(handle)
166 }
167
168 fn from_context(context: ContextId) -> Self {
169 Self { context }
170 }
171
172 fn initialize(self) -> Result<(), DbError> {
173 self.with_context(|| {
174 crate::sqlite_vfs::register();
175 Superblock::load()?;
176 stable_blob::ensure_current_layout()?;
177 Ok(())
178 })
179 }
180
181 fn with_context<T>(self, f: impl FnOnce() -> Result<T, DbError>) -> Result<T, DbError> {
182 memory::with_context(self.context, f)
183 }
184
185 pub fn update<T, F>(self, f: F) -> Result<T, DbError>
186 where
187 F: FnOnce(&mut UpdateConnection<'_>) -> Result<T, DbError>,
188 {
189 self.with_context(|| {
190 reject_active_read_connection(self.context)?;
191 clear_read_connection(self.context);
192 let db_size = stable_blob::begin_update()?;
193 let _overlay_guard = OverlayGuard;
194 let connection = write_connection(self.context, db_size)?;
195 let result = transaction::run_immediate(&connection, f);
196 clear_read_connection(self.context);
197 if result.is_err() {
198 clear_write_connection(self.context);
199 }
200 result
201 })
202 }
203
204 pub fn query<T, F>(self, f: F) -> Result<T, DbError>
205 where
206 F: FnOnce(&Connection) -> Result<T, DbError>,
207 {
208 self.with_context(|| with_read_connection(self.context, f))
209 }
210
211 pub fn migrate(self, migrations: &[migrate::Migration]) -> Result<(), DbError> {
212 self.update(|connection| migrate::apply(connection, migrations))?;
213 self.with_context(|| {
214 let target_version = migrations
215 .iter()
216 .map(|migration| migration.version)
217 .max()
218 .unwrap_or(0);
219 let mut block = Superblock::load()?;
220 if block.schema_version < target_version {
221 clear_read_connection(self.context);
222 block.schema_version = target_version;
223 block.store()?;
224 }
225 Ok(())
226 })
227 }
228
229 pub fn integrity_check(self) -> Result<String, DbError> {
230 self.query(|connection| {
231 connection.query_scalar::<String>("PRAGMA integrity_check", crate::params![])
232 })
233 }
234
235 pub fn db_checksum(self) -> Result<u64, DbError> {
236 self.with_context(|| stable_blob::checksum().map_err(DbError::from))
237 }
238
239 pub fn refresh_checksum(self) -> Result<u64, DbError> {
240 self.with_context(|| {
241 reject_active_read_connection(self.context)?;
242 clear_read_connection(self.context);
243 stable_blob::refresh_checksum().map_err(DbError::from)
244 })
245 }
246
247 pub fn refresh_checksum_chunk(self, max_bytes: u64) -> Result<ChecksumRefresh, DbError> {
248 self.with_context(|| {
249 reject_active_read_connection(self.context)?;
250 clear_read_connection(self.context);
251 stable_blob::refresh_checksum_chunk(max_bytes).map_err(DbError::from)
252 })
253 }
254}
255
256fn write_connection(context: ContextId, db_size: u64) -> Result<Rc<Connection>, DbError> {
257 WRITE_CONNECTIONS.with(|slot| {
258 let cached = { slot.borrow().get(&context).cloned() };
259 if let Some(connection) = cached {
260 return Ok(connection);
261 }
262 let connection = if db_size == 0 {
263 connection::open_read_write()?
264 } else {
265 connection::open_read_write_existing()?
266 };
267 let connection = Rc::new(connection);
268 slot.borrow_mut().insert(context, Rc::clone(&connection));
269 Ok(connection)
270 })
271}
272
273fn with_read_connection<T>(
274 context: ContextId,
275 f: impl FnOnce(&Connection) -> Result<T, DbError>,
276) -> Result<T, DbError> {
277 READ_CONNECTIONS.with(|slot| {
278 let cached = { slot.borrow().get(&context).cloned() };
279 let connection = if let Some(connection) = cached {
280 connection
281 } else {
282 let connection = Rc::new(connection::open_read_only()?);
283 slot.borrow_mut().insert(context, Rc::clone(&connection));
284 connection
285 };
286 let _guard = ReadGuard::enter(context);
287 f(&connection)
288 })
289}
290
291fn reject_active_read_connection(context: ContextId) -> Result<(), DbError> {
292 ACTIVE_READ_CONNECTIONS.with(|slot| {
293 let slot = slot.borrow();
294 let active = active_read_index(&slot, context)
295 .map(|index| slot[index].1)
296 .unwrap_or(0);
297 if active == 0 {
298 Ok(())
299 } else {
300 Err(DbError::ReadConnectionInUse)
301 }
302 })
303}
304
305fn clear_read_connection(context: ContextId) {
306 READ_CONNECTIONS.with(|slot| {
307 slot.borrow_mut().remove(&context);
308 });
309}
310
311fn clear_write_connection(context: ContextId) {
312 WRITE_CONNECTIONS.with(|slot| {
313 slot.borrow_mut().remove(&context);
314 });
315}
316
317struct ReadGuard {
318 context: ContextId,
319}
320
321impl ReadGuard {
322 fn enter(context: ContextId) -> Self {
323 ACTIVE_READ_CONNECTIONS.with(|slot| {
324 let mut slot = slot.borrow_mut();
325 if slot.is_empty() {
326 slot.push((context, 1));
327 return;
328 }
329 if let Some(index) = active_read_index(&slot, context) {
330 slot[index].1 += 1;
331 } else {
332 slot.push((context, 1));
333 }
334 });
335 Self { context }
336 }
337}
338
339impl Drop for ReadGuard {
340 fn drop(&mut self) {
341 ACTIVE_READ_CONNECTIONS.with(|slot| {
342 let mut slot = slot.borrow_mut();
343 if slot.len() == 1 && slot[0].0 == self.context {
344 let depth = &mut slot[0].1;
345 *depth = depth.saturating_sub(1);
346 if *depth == 0 {
347 slot.clear();
348 }
349 return;
350 }
351 let Some(index) = active_read_index(&slot, self.context) else {
352 return;
353 };
354 let depth = &mut slot[index].1;
355 *depth = depth.saturating_sub(1);
356 if *depth == 0 {
357 slot.swap_remove(index);
358 }
359 });
360 }
361}
362
363fn active_read_index(entries: &[(ContextId, usize)], context: ContextId) -> Option<usize> {
364 entries
365 .iter()
366 .position(|(stored_context, _)| *stored_context == context)
367}
368
369struct OverlayGuard;
370
371impl Drop for OverlayGuard {
372 fn drop(&mut self) {
373 stable_blob::rollback_update();
374 }
375}