diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 1c13e8d66..3ce9daf82 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -317,7 +317,7 @@ impl Drop for Connection { #[allow(clippy::arc_with_non_send_sync)] #[pyfunction(signature = (path))] pub fn connect(path: &str) -> Result { - match turso_core::Connection::from_uri(path, true, false, false, false, false, false) { + match turso_core::Connection::from_uri(path, true, false, false, false, false, false, false) { Ok((io, conn)) => Ok(Connection { conn, _io: io }), Err(e) => Err(PyErr::new::(format!( "Failed to create connection: {e:?}" diff --git a/cli/app.rs b/cli/app.rs index a31cafc73..fd776b28c 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -80,6 +80,8 @@ pub struct Opts { pub experimental_encryption: bool, #[clap(long, help = "Enable experimental index method feature")] pub experimental_index_method: bool, + #[clap(long, help = "Enable experimental autovacuum feature")] + pub experimental_autovacuum: bool, } const PROMPT: &str = "turso> "; @@ -195,6 +197,7 @@ impl Limbo { opts.experimental_strict, opts.experimental_encryption, opts.experimental_index_method, + opts.experimental_autovacuum, )? } else { let flags = if opts.readonly { @@ -213,6 +216,7 @@ impl Limbo { .with_strict(opts.experimental_strict) .with_encryption(opts.experimental_encryption) .with_index_method(opts.experimental_index_method) + .with_autovacuum(opts.experimental_autovacuum) .turso_cli(), None, )?; diff --git a/cli/mcp_server.rs b/cli/mcp_server.rs index 0798e130b..e4f1083f1 100644 --- a/cli/mcp_server.rs +++ b/cli/mcp_server.rs @@ -408,7 +408,7 @@ impl TursoMcpServer { // Open the new database connection let conn = if path == ":memory:" || path.contains([':', '?', '&', '#']) { - match Connection::from_uri(&path, true, false, false, false, false, false) { + match Connection::from_uri(&path, true, false, false, false, false, false, false) { Ok((_io, c)) => c, Err(e) => return format!("Failed to open database '{path}': {e}"), } @@ -417,7 +417,9 @@ impl TursoMcpServer { &path, None::<&str>, OpenFlags::default(), - DatabaseOpts::new().with_indexes(true), + DatabaseOpts::new() + .with_indexes(true) + .with_autovacuum(false), None, ) { Ok((_io, db)) => match db.connect() { diff --git a/core/incremental/cursor.rs b/core/incremental/cursor.rs index 0afab4d7a..470f6bbc1 100644 --- a/core/incremental/cursor.rs +++ b/core/incremental/cursor.rs @@ -317,6 +317,7 @@ mod tests { enable_load_extension: false, enable_encryption: false, enable_index_method: false, + enable_autovacuum: false, }, None, )?; diff --git a/core/lib.rs b/core/lib.rs index 128eb6a1f..f9b11580c 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -120,6 +120,7 @@ pub struct DatabaseOpts { pub enable_strict: bool, pub enable_encryption: bool, pub enable_index_method: bool, + pub enable_autovacuum: bool, enable_load_extension: bool, } @@ -132,6 +133,7 @@ impl Default for DatabaseOpts { enable_strict: false, enable_encryption: false, enable_index_method: false, + enable_autovacuum: false, enable_load_extension: false, } } @@ -177,6 +179,11 @@ impl DatabaseOpts { self.enable_index_method = enable; self } + + pub fn with_autovacuum(mut self, enable: bool) -> Self { + self.enable_autovacuum = enable; + self + } } #[derive(Clone, Debug, Default)] @@ -583,11 +590,24 @@ impl Database { AutoVacuumMode::None }; - pager.set_auto_vacuum_mode(mode); + // Force autovacuum to None if the experimental flag is not enabled + let final_mode = if !self.opts.enable_autovacuum { + if mode != AutoVacuumMode::None { + tracing::warn!( + "Database has autovacuum enabled but --experimental-autovacuum flag is not set. Forcing autovacuum to None." + ); + } + AutoVacuumMode::None + } else { + mode + }; + + pager.set_auto_vacuum_mode(final_mode); tracing::debug!( - "Opened existing database. Detected auto_vacuum_mode from header: {:?}", - mode + "Opened existing database. Detected auto_vacuum_mode from header: {:?}, final mode: {:?}", + mode, + final_mode ); } @@ -1510,6 +1530,7 @@ impl Connection { } #[cfg(feature = "fs")] + #[allow(clippy::too_many_arguments)] pub fn from_uri( uri: &str, use_indexes: bool, @@ -1520,6 +1541,8 @@ impl Connection { encryption: bool, // flag to opt-in custom modules support custom_modules: bool, + // flag to opt-in autovacuum support + autovacuum: bool, ) -> Result<(Arc, Arc)> { use crate::util::MEMORY_PATH; let opts = OpenOptions::parse(uri)?; @@ -1536,7 +1559,8 @@ impl Connection { .with_views(views) .with_strict(strict) .with_encryption(encryption) - .with_index_method(custom_modules), + .with_index_method(custom_modules) + .with_autovacuum(autovacuum), None, )?; let conn = db.connect()?; @@ -1566,7 +1590,8 @@ impl Connection { .with_views(views) .with_strict(strict) .with_encryption(encryption) - .with_index_method(custom_modules), + .with_index_method(custom_modules) + .with_autovacuum(autovacuum), encryption_opts.clone(), )?; if let Some(modeof) = opts.modeof { diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 676ec79a9..0f8f3d783 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -390,7 +390,7 @@ struct CommitInfo { } /// Track the state of the auto-vacuum mode. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum AutoVacuumMode { None, Full, diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index 8d1db5ae3..a9c5aa465 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -241,6 +241,14 @@ fn update_pragma( Ok((program, TransactionMode::None)) } PragmaName::AutoVacuum => { + // Check if autovacuum is enabled in database opts + if !connection.db.opts.enable_autovacuum { + return Err(LimboError::InvalidArgument( + "Autovacuum is not enabled. Use --experimental-autovacuum flag to enable it." + .to_string(), + )); + } + let is_empty = is_database_empty(resolver.schema, &pager)?; tracing::debug!( "Checking if database is empty for auto_vacuum pragma: {}", diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 42aab9df6..484db67c1 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -1225,11 +1225,15 @@ fn reopen_database(env: &mut SimulatorEnv) { } SimulationType::Default | SimulationType::Doublecheck => { env.db = None; - let db = match turso_core::Database::open_file( + let db = match turso_core::Database::open_file_with_flags( env.io.clone(), env.get_db_path().to_str().expect("path should be 'to_str'"), - mvcc, - indexes, + turso_core::OpenFlags::default(), + turso_core::DatabaseOpts::new() + .with_mvcc(mvcc) + .with_indexes(indexes) + .with_autovacuum(true), + None, ) { Ok(db) => db, Err(e) => { diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 23267519b..22022c8ae 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -235,11 +235,15 @@ impl SimulatorEnv { } self.db = None; - let db = match Database::open_file( + let db = match Database::open_file_with_flags( io.clone(), db_path.to_str().unwrap(), - self.profile.experimental_mvcc, - self.profile.query.gen_opts.indexes, + turso_core::OpenFlags::default(), + turso_core::DatabaseOpts::new() + .with_mvcc(self.profile.experimental_mvcc) + .with_indexes(self.profile.query.gen_opts.indexes) + .with_autovacuum(true), + None, ) { Ok(db) => db, Err(e) => { @@ -382,11 +386,15 @@ impl SimulatorEnv { ) }; - let db = match Database::open_file( + let db = match Database::open_file_with_flags( io.clone(), db_path.to_str().unwrap(), - profile.experimental_mvcc, - profile.query.gen_opts.indexes, + turso_core::OpenFlags::default(), + turso_core::DatabaseOpts::new() + .with_mvcc(profile.experimental_mvcc) + .with_indexes(profile.query.gen_opts.indexes) + .with_autovacuum(true), + None, ) { Ok(db) => db, Err(e) => { diff --git a/tests/integration/query_processing/encryption.rs b/tests/integration/query_processing/encryption.rs index de6aa012b..ea10f4221 100644 --- a/tests/integration/query_processing/encryption.rs +++ b/tests/integration/query_processing/encryption.rs @@ -54,6 +54,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, )?; let mut row_count = 0; run_query_on_row(&tmp_db, &conn, "SELECT * FROM test", |row: &Row| { @@ -77,6 +78,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, )?; run_query( &tmp_db, @@ -99,6 +101,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, )?; run_query( &tmp_db, @@ -129,6 +132,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, )?; let should_panic = panic::catch_unwind(panic::AssertUnwindSafe(|| { run_query_on_row(&tmp_db, &conn, "SELECT * FROM test", |_row: &Row| {}).unwrap(); @@ -150,6 +154,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, ) .unwrap(); })); @@ -172,6 +177,7 @@ fn test_per_page_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, ) .unwrap(); })); @@ -247,6 +253,7 @@ fn test_non_4k_page_size_encryption() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, )?; run_query_on_row(&tmp_db, &conn, "SELECT * FROM test", |row: &Row| { assert_eq!(row.get::(0).unwrap(), 1); @@ -313,6 +320,7 @@ fn test_corruption_turso_magic_bytes() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, ) .unwrap(); run_query_on_row(&tmp_db, &conn, "SELECT * FROM test", |_row: &Row| {}).unwrap(); @@ -406,6 +414,7 @@ fn test_corruption_associated_data_bytes() -> anyhow::Result<()> { false, ENABLE_ENCRYPTION, false, + false, ) .unwrap(); run_query_on_row(&test_tmp_db, &conn, "SELECT * FROM test", |_row: &Row| {})