From 17da71ee3c4b0992ac98f487d4b5c1866b2daff6 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Mon, 6 Oct 2025 21:33:20 -0400 Subject: [PATCH 1/3] Open db with proper IO when attaching database to fix #3540 --- core/lib.rs | 23 ++++++++++++++++------- core/vdbe/execute.rs | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index 11b85be81..fb31a557d 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -62,6 +62,7 @@ pub use io::{ }; use parking_lot::RwLock; use schema::Schema; +use std::cell::Cell; use std::{ borrow::Cow, cell::RefCell, @@ -217,7 +218,7 @@ pub struct Database { shared_wal: Arc>, db_state: Arc, init_lock: Arc>, - open_flags: OpenFlags, + open_flags: Cell, builtin_syms: RwLock, opts: DatabaseOpts, n_connections: AtomicUsize, @@ -231,7 +232,7 @@ impl fmt::Debug for Database { let mut debug_struct = f.debug_struct("Database"); debug_struct .field("path", &self.path) - .field("open_flags", &self.open_flags); + .field("open_flags", &self.open_flags.get()); // Database state information let db_state_value = match self.db_state.get() { @@ -468,7 +469,7 @@ impl Database { db_file, builtin_syms: syms.into(), io: io.clone(), - open_flags: flags, + open_flags: flags.into(), db_state: Arc::new(AtomicDbState::new(db_state)), init_lock: Arc::new(Mutex::new(())), opts, @@ -599,7 +600,7 @@ impl Database { } pub fn is_readonly(&self) -> bool { - self.open_flags.contains(OpenFlags::ReadOnly) + self.open_flags.get().contains(OpenFlags::ReadOnly) } /// If we do not have a physical WAL file, but we know the database file is initialized on disk, @@ -1529,7 +1530,6 @@ impl Connection { ) -> Result> { let mut opts = OpenOptions::parse(uri)?; // FIXME: for now, only support read only attach - opts.mode = OpenMode::ReadOnly; let flags = opts.get_flags()?; let io = opts.vfs.map(Database::io_for_vfs).unwrap_or(Ok(io))?; let db = Database::open_file_with_flags(io.clone(), &opts.path, flags, db_opts, None)?; @@ -2090,9 +2090,18 @@ impl Connection { .with_indexes(use_indexes) .with_views(use_views) .with_strict(use_strict); - let db = Self::from_uri_attached(path, db_opts, self.db.io.clone())?; + let is_memory = path.contains(":memory:"); + let io: Arc = if is_memory { + Arc::new(MemoryIO::new()) + } else { + Arc::new(PlatformIO::new()?) + }; + let db = Self::from_uri_attached(path, db_opts, io).expect("FAILURE"); let pager = Arc::new(db.init_pager(None)?); - + // set back to read-only now that we have + if is_memory { + db.open_flags.set(OpenFlags::ReadOnly); + } self.attached_databases.write().insert(alias, (db, pager)); Ok(()) diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 87fbaec0a..a911cd527 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -2258,7 +2258,7 @@ pub fn op_transaction_inner( OpTransactionState::Start => { let conn = program.connection.clone(); let write = matches!(tx_mode, TransactionMode::Write); - if write && conn.db.open_flags.contains(OpenFlags::ReadOnly) { + if write && conn.db.open_flags.get().contains(OpenFlags::ReadOnly) { return Err(LimboError::ReadOnly); } From addb9ef65bda2952915336099d29e64b7ba3c0e4 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Mon, 6 Oct 2025 21:33:42 -0400 Subject: [PATCH 2/3] Add regression test for #3540 attach issue --- testing/attach.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testing/attach.test b/testing/attach.test index 0d799a927..58d71eea1 100755 --- a/testing/attach.test +++ b/testing/attach.test @@ -73,3 +73,11 @@ do_execsql_test_error query-after-detach { DETACH DATABASE small; select * from small.sqlite_schema; } {(.*no such.*)} + +# regression test for https://github.com/tursodatabase/turso/issues/3540 +do_execsql_test_on_specifc_db {:memory:} attach-from-memory-db { + CREATE TABLE t(a); + INSERT INTO t SELECT value from generate_series(1,10); + ATTACH DATABASE 'testing/testing.db' as a; + SELECT * from a.products, t LIMIT 1; +} {1|hat|79.0|1} From 20d2ca55fe5a623c048332e8e9d5e54c0b91a8fa Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Mon, 6 Oct 2025 21:43:48 -0400 Subject: [PATCH 3/3] fix clippy warning --- core/lib.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index fb31a557d..d5f9281b3 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1528,8 +1528,7 @@ impl Connection { db_opts: DatabaseOpts, io: Arc, ) -> Result> { - let mut opts = OpenOptions::parse(uri)?; - // FIXME: for now, only support read only attach + let opts = OpenOptions::parse(uri)?; let flags = opts.get_flags()?; let io = opts.vfs.map(Database::io_for_vfs).unwrap_or(Ok(io))?; let db = Database::open_file_with_flags(io.clone(), &opts.path, flags, db_opts, None)?; @@ -2090,18 +2089,15 @@ impl Connection { .with_indexes(use_indexes) .with_views(use_views) .with_strict(use_strict); - let is_memory = path.contains(":memory:"); - let io: Arc = if is_memory { + let io: Arc = if path.contains(":memory:") { Arc::new(MemoryIO::new()) } else { Arc::new(PlatformIO::new()?) }; - let db = Self::from_uri_attached(path, db_opts, io).expect("FAILURE"); + let db = Self::from_uri_attached(path, db_opts, io)?; let pager = Arc::new(db.init_pager(None)?); - // set back to read-only now that we have - if is_memory { - db.open_flags.set(OpenFlags::ReadOnly); - } + // FIXME: for now, only support read only attach + db.open_flags.set(OpenFlags::ReadOnly); self.attached_databases.write().insert(alias, (db, pager)); Ok(())