From 44ed4d562fd4e240ae07117c7e20adda37b56f9e Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Thu, 28 Aug 2025 15:20:23 +0300 Subject: [PATCH] core: Initial pass on synchronous pragma This adds support for "OFF" and "FULL" (default) synchronous modes. As future work, we need to add NORMAL and EXTRA as well because applications expect them. --- COMPAT.md | 2 +- core/lib.rs | 21 ++++++++++++++++++++- core/pragma.rs | 4 ++++ core/storage/pager.rs | 21 +++++++++++++++++---- core/translate/pragma.rs | 29 +++++++++++++++++++++++++++++ parser/src/ast.rs | 2 ++ 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index a81641055..8b2bd63a4 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -160,7 +160,7 @@ Turso aims to be fully compatible with SQLite, with opt-in features not supporte | PRAGMA shrink_memory | No | | | PRAGMA soft_heap_limit | No | | | PRAGMA stats | No | Used for testing in SQLite | -| PRAGMA synchronous | No | | +| PRAGMA synchronous | Partial | `OFF` and `FULL` supported | | PRAGMA table_info | Yes | | | PRAGMA table_list | No | | | PRAGMA table_xinfo | No | | diff --git a/core/lib.rs b/core/lib.rs index a5055fd6f..e531a37b6 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -109,6 +109,12 @@ enum TransactionState { None, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SyncMode { + Off = 0, + Full = 2, +} + pub(crate) type MvStore = mvcc::MvStore; pub(crate) type MvCursor = mvcc::cursor::MvccLazyCursor; @@ -466,6 +472,7 @@ impl Database { is_nested_stmt: Cell::new(false), encryption_key: RefCell::new(None), encryption_cipher_mode: Cell::new(None), + sync_mode: Cell::new(SyncMode::Full), }); self.n_connections .fetch_add(1, std::sync::atomic::Ordering::Relaxed); @@ -900,6 +907,7 @@ pub struct Connection { is_nested_stmt: Cell, encryption_key: RefCell>, encryption_cipher_mode: Cell>, + sync_mode: Cell, } impl Drop for Connection { @@ -1459,7 +1467,10 @@ impl Connection { }; let commit_err = if force_commit { - pager.io.block(|| pager.commit_dirty_pages(true)).err() + pager + .io + .block(|| pager.commit_dirty_pages(true, self.get_sync_mode())) + .err() } else { None }; @@ -1982,6 +1993,14 @@ impl Connection { self.query_only.set(value); } + pub fn get_sync_mode(&self) -> SyncMode { + self.sync_mode.get() + } + + pub fn set_sync_mode(&self, mode: SyncMode) { + self.sync_mode.set(mode); + } + /// Creates a HashSet of modules that have been loaded pub fn get_syms_vtab_mods(&self) -> std::collections::HashSet { self.syms.borrow().vtab_modules.keys().cloned().collect() diff --git a/core/pragma.rs b/core/pragma.rs index e006963c0..5ec791e3a 100644 --- a/core/pragma.rs +++ b/core/pragma.rs @@ -81,6 +81,10 @@ pub fn pragma_for(pragma: &PragmaName) -> Pragma { PragmaFlags::NoColumns1 | PragmaFlags::Result0, &["schema_version"], ), + Synchronous => Pragma::new( + PragmaFlags::NoColumns1 | PragmaFlags::Result0, + &["synchronous"], + ), TableInfo => Pragma::new( PragmaFlags::NeedSchema | PragmaFlags::Result1 | PragmaFlags::SchemaOpt, &["cid", "name", "type", "notnull", "dflt_value", "pk"], diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 003c13b61..af44a10e3 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -1063,8 +1063,10 @@ impl Pager { self.rollback(schema_did_change, connection, is_write)?; return Ok(IOResult::Done(PagerCommitResult::Rollback)); } - let commit_status = - return_if_io!(self.commit_dirty_pages(connection.wal_auto_checkpoint_disabled.get())); + let commit_status = return_if_io!(self.commit_dirty_pages( + connection.wal_auto_checkpoint_disabled.get(), + connection.get_sync_mode() + )); wal.borrow().end_write_tx(); wal.borrow().end_read_tx(); @@ -1310,6 +1312,7 @@ impl Pager { pub fn commit_dirty_pages( &self, wal_auto_checkpoint_disabled: bool, + sync_mode: crate::SyncMode, ) -> Result> { let Some(wal) = self.wal.as_ref() else { return Err(LimboError::InternalError( @@ -1386,7 +1389,12 @@ impl Pager { if completions.is_empty() { return Ok(IOResult::Done(PagerCommitResult::WalWritten)); } else { - self.commit_info.state.set(CommitState::SyncWal); + // Skip sync if synchronous mode is OFF + if sync_mode == crate::SyncMode::Off { + self.commit_info.state.set(CommitState::AfterSyncWal); + } else { + self.commit_info.state.set(CommitState::SyncWal); + } } if !completions.iter().all(|c| c.is_completed()) { io_yield_many!(completions); @@ -1409,7 +1417,12 @@ impl Pager { } CommitState::Checkpoint => { checkpoint_result = return_if_io!(self.checkpoint()); - self.commit_info.state.set(CommitState::SyncDbFile); + // Skip sync if synchronous mode is OFF + if sync_mode == crate::SyncMode::Off { + self.commit_info.state.set(CommitState::AfterSyncDbFile); + } else { + self.commit_info.state.set(CommitState::SyncDbFile); + } } CommitState::SyncDbFile => { let c = sqlite3_ondisk::begin_sync(self.db_file.clone(), self.syncing.clone())?; diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 43abf02e7..b04d0e87e 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -324,6 +324,27 @@ fn update_pragma( connection.set_encryption_cipher(cipher); Ok((program, TransactionMode::None)) } + PragmaName::Synchronous => { + use crate::SyncMode; + + let mode = match value { + Expr::Name(name) => { + let name_upper = name.as_str().to_uppercase(); + match name_upper.as_str() { + "OFF" | "FALSE" | "NO" | "0" => SyncMode::Off, + _ => SyncMode::Full, + } + } + Expr::Literal(Literal::Numeric(n)) => match n.as_str() { + "0" => SyncMode::Off, + _ => SyncMode::Full, + }, + _ => SyncMode::Full, + }; + + connection.set_sync_mode(mode); + Ok((program, TransactionMode::None)) + } } } @@ -604,6 +625,14 @@ fn query_pragma( } Ok((program, TransactionMode::None)) } + PragmaName::Synchronous => { + let mode = connection.get_sync_mode(); + let register = program.alloc_register(); + program.emit_int(mode as i64, register); + program.emit_result_row(register, 1); + program.add_pragma_result_column(pragma.to_string()); + Ok((program, TransactionMode::None)) + } } } diff --git a/parser/src/ast.rs b/parser/src/ast.rs index a452039bb..a0640aac5 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -1360,6 +1360,8 @@ pub enum PragmaName { QueryOnly, /// Returns schema version of the database file. SchemaVersion, + /// Control database synchronization mode (OFF | FULL | NORMAL | EXTRA) + Synchronous, /// returns information about the columns of a table TableInfo, /// enable capture-changes logic for the connection