From 21535018aa86b83964d084b52634cdb8f225d164 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 20 May 2025 10:44:55 +0300 Subject: [PATCH 1/7] core: Don't pass page to begin_read_wal_frame() Make `begin_read_wal_frame()` a bit more generic by not requiring a page to be passed. --- core/storage/sqlite3_ondisk.rs | 15 +++------------ core/storage/wal.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 348465db7..30035d1ab 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -823,7 +823,7 @@ pub fn begin_read_page( Ok(()) } -fn finish_read_page( +pub fn finish_read_page( page_idx: usize, buffer_ref: Arc>, page: PageRef, @@ -1528,13 +1528,9 @@ pub fn begin_read_wal_frame( io: &Arc, offset: usize, buffer_pool: Rc, - page: PageRef, + complete: Box>) -> ()>, ) -> Result<()> { - trace!( - "begin_read_wal_frame(offset={}, page={})", - offset, - page.get().id - ); + trace!("begin_read_wal_frame(offset={})", offset); let buf = buffer_pool.get(); let drop_fn = Rc::new(move |buf| { let buffer_pool = buffer_pool.clone(); @@ -1542,11 +1538,6 @@ pub fn begin_read_wal_frame( }); #[allow(clippy::arc_with_non_send_sync)] let buf = Arc::new(RefCell::new(Buffer::new(buf, drop_fn))); - let frame = page.clone(); - let complete = Box::new(move |buf: Arc>| { - let frame = frame.clone(); - finish_read_page(page.get().id, buf, frame).unwrap(); - }); let c = Completion::Read(ReadCompletion::new(buf, complete)); io.pread(offset, c)?; Ok(()) diff --git a/core/storage/wal.rs b/core/storage/wal.rs index 760507ec3..edb657f39 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -10,7 +10,8 @@ use crate::fast_lock::SpinLock; use crate::io::{File, SyncCompletion, IO}; use crate::result::LimboResult; use crate::storage::sqlite3_ondisk::{ - begin_read_wal_frame, begin_write_wal_frame, WAL_FRAME_HEADER_SIZE, WAL_HEADER_SIZE, + begin_read_wal_frame, begin_write_wal_frame, finish_read_page, WAL_FRAME_HEADER_SIZE, + WAL_HEADER_SIZE, }; use crate::{Buffer, Result}; use crate::{Completion, Page}; @@ -520,11 +521,16 @@ impl Wal for WalFile { debug!("read_frame({})", frame_id); let offset = self.frame_offset(frame_id); page.set_locked(); + let frame = page.clone(); + let complete = Box::new(move |buf: Arc>| { + let frame = frame.clone(); + finish_read_page(page.get().id, buf, frame).unwrap(); + }); begin_read_wal_frame( &self.get_shared().file, offset + WAL_FRAME_HEADER_SIZE, buffer_pool, - page, + complete, )?; Ok(()) } From eca9a5b703fe76c80edba7b36553c8a15ef41b78 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Thu, 22 May 2025 09:32:16 +0300 Subject: [PATCH 2/7] core/io: Switch to Arc --- bindings/javascript/src/lib.rs | 8 ++++---- bindings/wasm/lib.rs | 18 +++++++++--------- core/io/generic.rs | 8 ++++---- core/io/io_uring.rs | 8 +++++--- core/io/memory.rs | 6 +++--- core/io/mod.rs | 6 +++--- core/io/unix.rs | 10 +++++----- core/io/vfs.rs | 8 ++++---- core/lib.rs | 5 ++++- core/storage/btree.rs | 5 +++-- core/storage/database.rs | 20 ++++++++++---------- core/storage/sqlite3_ondisk.rs | 29 ++++++++++++++++++----------- core/storage/wal.rs | 4 +++- simulator/runner/file.rs | 6 +++--- 14 files changed, 78 insertions(+), 63 deletions(-) diff --git a/bindings/javascript/src/lib.rs b/bindings/javascript/src/lib.rs index 7d84d6cc3..4b029fdc9 100644 --- a/bindings/javascript/src/lib.rs +++ b/bindings/javascript/src/lib.rs @@ -444,8 +444,8 @@ impl DatabaseFile { } impl limbo_core::DatabaseStorage for DatabaseFile { - fn read_page(&self, page_idx: usize, c: limbo_core::Completion) -> limbo_core::Result<()> { - let r = match c { + fn read_page(&self, page_idx: usize, c: Arc) -> limbo_core::Result<()> { + let r = match *c { limbo_core::Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -463,7 +463,7 @@ impl limbo_core::DatabaseStorage for DatabaseFile { &self, page_idx: usize, buffer: Arc>, - c: limbo_core::Completion, + c: Arc, ) -> limbo_core::Result<()> { let size = buffer.borrow().len(); let pos = (page_idx - 1) * size; @@ -471,7 +471,7 @@ impl limbo_core::DatabaseStorage for DatabaseFile { Ok(()) } - fn sync(&self, c: limbo_core::Completion) -> limbo_core::Result<()> { + fn sync(&self, c: Arc) -> limbo_core::Result<()> { self.file.sync(c) } } diff --git a/bindings/wasm/lib.rs b/bindings/wasm/lib.rs index ffbe90928..4277f1f3f 100644 --- a/bindings/wasm/lib.rs +++ b/bindings/wasm/lib.rs @@ -208,8 +208,8 @@ impl limbo_core::File for File { Ok(()) } - fn pread(&self, pos: usize, c: limbo_core::Completion) -> Result<()> { - let r = match &c { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { + let r = match *c { limbo_core::Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -227,9 +227,9 @@ impl limbo_core::File for File { &self, pos: usize, buffer: Arc>, - c: limbo_core::Completion, + c: Arc, ) -> Result<()> { - let w = match &c { + let w = match *c { limbo_core::Completion::Write(ref w) => w, _ => unreachable!(), }; @@ -240,7 +240,7 @@ impl limbo_core::File for File { Ok(()) } - fn sync(&self, c: limbo_core::Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { self.vfs.sync(self.fd); c.complete(0); Ok(()) @@ -326,8 +326,8 @@ impl DatabaseFile { } impl limbo_core::DatabaseStorage for DatabaseFile { - fn read_page(&self, page_idx: usize, c: limbo_core::Completion) -> Result<()> { - let r = match c { + fn read_page(&self, page_idx: usize, c: Arc) -> Result<()> { + let r = match *c { limbo_core::Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -345,7 +345,7 @@ impl limbo_core::DatabaseStorage for DatabaseFile { &self, page_idx: usize, buffer: Arc>, - c: limbo_core::Completion, + c: Arc, ) -> Result<()> { let size = buffer.borrow().len(); let pos = (page_idx - 1) * size; @@ -353,7 +353,7 @@ impl limbo_core::DatabaseStorage for DatabaseFile { Ok(()) } - fn sync(&self, _c: limbo_core::Completion) -> Result<()> { + fn sync(&self, _c: Arc) -> Result<()> { todo!() } } diff --git a/core/io/generic.rs b/core/io/generic.rs index aab5f2687..60e11f119 100644 --- a/core/io/generic.rs +++ b/core/io/generic.rs @@ -79,11 +79,11 @@ impl File for GenericFile { Ok(()) } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; { - let r = match c { + let r = match *c { Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -95,7 +95,7 @@ impl File for GenericFile { Ok(()) } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; let buf = buffer.borrow(); @@ -105,7 +105,7 @@ impl File for GenericFile { Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { let mut file = self.file.borrow_mut(); file.sync_all().map_err(|err| LimboError::IOError(err))?; c.complete(0); diff --git a/core/io/io_uring.rs b/core/io/io_uring.rs index 25d6aa33e..44d4c5696 100644 --- a/core/io/io_uring.rs +++ b/core/io/io_uring.rs @@ -1,3 +1,5 @@ +#![allow(clippy::arc_with_non_send_sync)] + use super::{common, Completion, File, OpenFlags, WriteCompletion, IO}; use crate::io::clock::{Clock, Instant}; use crate::{LimboError, MemoryIO, Result}; @@ -93,7 +95,7 @@ impl InnerUringIO { } impl WrappedIOUring { - fn submit_entry(&mut self, entry: &io_uring::squeue::Entry, c: Completion) { + fn submit_entry(&mut self, entry: &io_uring::squeue::Entry, c: Arc) { trace!("submit_entry({:?})", entry); self.pending[entry.get_user_data() as usize] = Some(c); unsafe { @@ -263,7 +265,7 @@ impl File for UringFile { Ok(()) } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { let r = c.as_read(); trace!("pread(pos = {}, length = {})", pos, r.buf().len()); let fd = io_uring::types::Fd(self.file.as_raw_fd()); @@ -282,7 +284,7 @@ impl File for UringFile { Ok(()) } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let mut io = self.io.borrow_mut(); let fd = io_uring::types::Fd(self.file.as_raw_fd()); let write = { diff --git a/core/io/memory.rs b/core/io/memory.rs index 9cc56a5e3..cb20c6e63 100644 --- a/core/io/memory.rs +++ b/core/io/memory.rs @@ -79,7 +79,7 @@ impl File for MemoryFile { Ok(()) } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { let r = c.as_read(); let buf_len = r.buf().len(); if buf_len == 0 { @@ -120,7 +120,7 @@ impl File for MemoryFile { Ok(()) } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let buf = buffer.borrow(); let buf_len = buf.len(); if buf_len == 0 { @@ -156,7 +156,7 @@ impl File for MemoryFile { Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { // no-op c.complete(0); Ok(()) diff --git a/core/io/mod.rs b/core/io/mod.rs index 6f75e9bea..4cbe785c5 100644 --- a/core/io/mod.rs +++ b/core/io/mod.rs @@ -14,9 +14,9 @@ use std::{ pub trait File: Send + Sync { fn lock_file(&self, exclusive: bool) -> Result<()>; fn unlock_file(&self) -> Result<()>; - fn pread(&self, pos: usize, c: Completion) -> Result<()>; - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()>; - fn sync(&self, c: Completion) -> Result<()>; + fn pread(&self, pos: usize, c: Arc) -> Result<()>; + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()>; + fn sync(&self, c: Arc) -> Result<()>; fn size(&self) -> Result; } diff --git a/core/io/unix.rs b/core/io/unix.rs index 721ba20f3..8c2f121e3 100644 --- a/core/io/unix.rs +++ b/core/io/unix.rs @@ -268,10 +268,10 @@ impl IO for UnixIO { } enum CompletionCallback { - Read(Arc>, Completion, usize), + Read(Arc>, Arc, usize), Write( Arc>, - Completion, + Arc, Arc>, usize, ), @@ -326,7 +326,7 @@ impl File for UnixFile<'_> { Ok(()) } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { let file = self.file.borrow(); let result = { let r = c.as_read(); @@ -358,7 +358,7 @@ impl File for UnixFile<'_> { } } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let file = self.file.borrow(); let result = { let buf = buffer.borrow(); @@ -387,7 +387,7 @@ impl File for UnixFile<'_> { } } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { let file = self.file.borrow(); let result = fs::fsync(file.as_fd()); match result { diff --git a/core/io/vfs.rs b/core/io/vfs.rs index d02f7d345..7e953ebd0 100644 --- a/core/io/vfs.rs +++ b/core/io/vfs.rs @@ -93,8 +93,8 @@ impl File for VfsFileImpl { Ok(()) } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { - let r = match &c { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { + let r = match &*c { Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -112,7 +112,7 @@ impl File for VfsFileImpl { } } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let buf = buffer.borrow(); let count = buf.as_slice().len(); if self.vfs.is_null() { @@ -136,7 +136,7 @@ impl File for VfsFileImpl { } } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { let vfs = unsafe { &*self.vfs }; let result = unsafe { (vfs.sync)(self.file) }; if result < 0 { diff --git a/core/lib.rs b/core/lib.rs index 038fc6218..e60d74502 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::arc_with_non_send_sync)] + mod error; mod ext; mod fast_lock; @@ -302,7 +304,8 @@ pub fn maybe_init_database_file(file: &Arc, io: &Arc) -> Resul let completion = Completion::Write(WriteCompletion::new(Box::new(move |_| { *flag_complete.borrow_mut() = true; }))); - file.pwrite(0, contents.buffer.clone(), completion)?; + #[allow(clippy::arc_with_non_send_sync)] + file.pwrite(0, contents.buffer.clone(), Arc::new(completion))?; } let mut limit = 100; loop { diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 57e88802a..735f0495e 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -6776,7 +6776,7 @@ mod tests { let write_complete = Box::new(|_| {}); let c = Completion::Write(WriteCompletion::new(write_complete)); - db_file.write_page(1, buf.clone(), c).unwrap(); + db_file.write_page(1, buf.clone(), Arc::new(c)).unwrap(); let wal_shared = WalFileShared::open_shared(&io, "test.wal", page_size).unwrap(); let wal = Rc::new(RefCell::new(WalFile::new( @@ -6826,9 +6826,10 @@ mod tests { ))); let write_complete = Box::new(|_| {}); let c = Completion::Write(WriteCompletion::new(write_complete)); + #[allow(clippy::arc_with_non_send_sync)] pager .db_file - .write_page(current_page as usize, buf.clone(), c)?; + .write_page(current_page as usize, buf.clone(), Arc::new(c))?; pager.io.run_once()?; let page = cursor.read_page(current_page as usize)?; diff --git a/core/storage/database.rs b/core/storage/database.rs index cf8b57d8e..db57892ae 100644 --- a/core/storage/database.rs +++ b/core/storage/database.rs @@ -8,14 +8,14 @@ use std::{cell::RefCell, sync::Arc}; /// the storage medium. A database can either be a file on disk, like in SQLite, /// or something like a remote page server service. pub trait DatabaseStorage: Send + Sync { - fn read_page(&self, page_idx: usize, c: Completion) -> Result<()>; + fn read_page(&self, page_idx: usize, c: Arc) -> Result<()>; fn write_page( &self, page_idx: usize, buffer: Arc>, - c: Completion, + c: Arc, ) -> Result<()>; - fn sync(&self, c: Completion) -> Result<()>; + fn sync(&self, c: Arc) -> Result<()>; } #[cfg(feature = "fs")] @@ -30,7 +30,7 @@ unsafe impl Sync for DatabaseFile {} #[cfg(feature = "fs")] impl DatabaseStorage for DatabaseFile { - fn read_page(&self, page_idx: usize, c: Completion) -> Result<()> { + fn read_page(&self, page_idx: usize, c: Arc) -> Result<()> { let r = c.as_read(); let size = r.buf().len(); assert!(page_idx > 0); @@ -46,7 +46,7 @@ impl DatabaseStorage for DatabaseFile { &self, page_idx: usize, buffer: Arc>, - c: Completion, + c: Arc, ) -> Result<()> { let buffer_size = buffer.borrow().len(); assert!(page_idx > 0); @@ -58,7 +58,7 @@ impl DatabaseStorage for DatabaseFile { Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { self.file.sync(c) } } @@ -78,8 +78,8 @@ unsafe impl Send for FileMemoryStorage {} unsafe impl Sync for FileMemoryStorage {} impl DatabaseStorage for FileMemoryStorage { - fn read_page(&self, page_idx: usize, c: Completion) -> Result<()> { - let r = match c { + fn read_page(&self, page_idx: usize, c: Arc) -> Result<()> { + let r = match *c { Completion::Read(ref r) => r, _ => unreachable!(), }; @@ -97,7 +97,7 @@ impl DatabaseStorage for FileMemoryStorage { &self, page_idx: usize, buffer: Arc>, - c: Completion, + c: Arc, ) -> Result<()> { let buffer_size = buffer.borrow().len(); assert!(buffer_size >= 512); @@ -108,7 +108,7 @@ impl DatabaseStorage for FileMemoryStorage { Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { self.file.sync(c) } } diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 30035d1ab..dcbed64d0 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -41,6 +41,8 @@ //! //! https://www.sqlite.org/fileformat.html +#![allow(clippy::arc_with_non_send_sync)] + use crate::error::LimboError; use crate::fast_lock::SpinLock; use crate::io::{Buffer, Complete, Completion, ReadCompletion, SyncCompletion, WriteCompletion}; @@ -293,7 +295,8 @@ pub fn begin_read_database_header( finish_read_database_header(buf, header).unwrap(); }); let c = Completion::Read(ReadCompletion::new(buf, complete)); - db_file.read_page(1, c)?; + #[allow(clippy::arc_with_non_send_sync)] + db_file.read_page(1, Arc::new(c))?; Ok(result) } @@ -355,7 +358,8 @@ pub fn begin_write_database_header(header: &DatabaseHeader, pager: &Pager) -> Re #[allow(clippy::arc_with_non_send_sync)] let buf = Arc::new(RefCell::new(Buffer::allocate(512, drop_fn))); let c = Completion::Read(ReadCompletion::new(buf, read_complete)); - page_source.read_page(1, c)?; + #[allow(clippy::arc_with_non_send_sync)] + page_source.read_page(1, Arc::new(c))?; // run get header block pager.io.run_once()?; @@ -369,7 +373,7 @@ pub fn begin_write_database_header(header: &DatabaseHeader, pager: &Pager) -> Re }); let c = Completion::Write(WriteCompletion::new(write_complete)); - page_source.write_page(1, buffer_to_copy, c)?; + page_source.write_page(1, buffer_to_copy, Arc::new(c))?; Ok(()) } @@ -819,7 +823,7 @@ pub fn begin_read_page( } }); let c = Completion::Read(ReadCompletion::new(buf, complete)); - db_file.read_page(page_idx, c)?; + db_file.read_page(page_idx, Arc::new(c))?; Ok(()) } @@ -877,7 +881,7 @@ pub fn begin_write_btree_page( }) }; let c = Completion::Write(WriteCompletion::new(write_complete)); - page_source.write_page(page_id, buffer.clone(), c)?; + page_source.write_page(page_id, buffer.clone(), Arc::new(c))?; Ok(()) } @@ -889,7 +893,8 @@ pub fn begin_sync(db_file: Arc, syncing: Rc>) *syncing.borrow_mut() = false; }), }); - db_file.sync(completion)?; + #[allow(clippy::arc_with_non_send_sync)] + db_file.sync(Arc::new(completion))?; Ok(()) } @@ -1519,7 +1524,7 @@ pub fn read_entire_wal_dumb(file: &Arc) -> Result, header: &WalHeader) -> Result< } }) }; - let c = Completion::Write(WriteCompletion::new(write_complete)); + #[allow(clippy::arc_with_non_send_sync)] + let c = Arc::new(Completion::Write(WriteCompletion::new(write_complete))); io.pwrite(0, buffer.clone(), c)?; Ok(()) } diff --git a/core/storage/wal.rs b/core/storage/wal.rs index edb657f39..4eab23e44 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -1,3 +1,5 @@ +#![allow(clippy::arc_with_non_send_sync)] + use std::cell::UnsafeCell; use std::collections::HashMap; use tracing::{debug, trace}; @@ -750,7 +752,7 @@ impl Wal for WalFile { *syncing.borrow_mut() = false; }), }); - shared.file.sync(completion)?; + shared.file.sync(Arc::new(completion))?; } self.sync_state.replace(SyncState::Syncing); Ok(WalFsyncStatus::IO) diff --git a/simulator/runner/file.rs b/simulator/runner/file.rs index 3e66a02fa..5d1c2cd4b 100644 --- a/simulator/runner/file.rs +++ b/simulator/runner/file.rs @@ -77,7 +77,7 @@ impl File for SimulatorFile { self.inner.unlock_file() } - fn pread(&self, pos: usize, c: limbo_core::Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { *self.nr_pread_calls.borrow_mut() += 1; if *self.fault.borrow() { *self.nr_pread_faults.borrow_mut() += 1; @@ -92,7 +92,7 @@ impl File for SimulatorFile { &self, pos: usize, buffer: Arc>, - c: limbo_core::Completion, + c: Arc, ) -> Result<()> { *self.nr_pwrite_calls.borrow_mut() += 1; if *self.fault.borrow() { @@ -104,7 +104,7 @@ impl File for SimulatorFile { self.inner.pwrite(pos, buffer, c) } - fn sync(&self, c: limbo_core::Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { *self.nr_sync_calls.borrow_mut() += 1; self.inner.sync(c) } From 05df548b104e77c624f8af15aa8995536ab2a18f Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Thu, 22 May 2025 09:43:28 +0300 Subject: [PATCH 3/7] core/io: Add wait_for_completion() to I/O dispatcher --- bindings/wasm/lib.rs | 7 +++++++ core/io/generic.rs | 7 +++++++ core/io/io_uring.rs | 15 +++++++++++---- core/io/memory.rs | 4 ++++ core/io/mod.rs | 32 +++++++++++++++++++++++++++++--- core/io/unix.rs | 7 +++++++ core/io/vfs.rs | 4 ++++ core/io/windows.rs | 13 ++++++++++--- core/storage/sqlite3_ondisk.rs | 1 + core/storage/wal.rs | 1 + simulator/runner/io.rs | 7 +++++++ 11 files changed, 88 insertions(+), 10 deletions(-) diff --git a/bindings/wasm/lib.rs b/bindings/wasm/lib.rs index 4277f1f3f..3bf8e14ff 100644 --- a/bindings/wasm/lib.rs +++ b/bindings/wasm/lib.rs @@ -283,6 +283,13 @@ impl limbo_core::IO for PlatformIO { })) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn run_once(&self) -> Result<()> { Ok(()) } diff --git a/core/io/generic.rs b/core/io/generic.rs index 60e11f119..11dfe9971 100644 --- a/core/io/generic.rs +++ b/core/io/generic.rs @@ -35,6 +35,13 @@ impl IO for GenericIO { })) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn run_once(&self) -> Result<()> { Ok(()) } diff --git a/core/io/io_uring.rs b/core/io/io_uring.rs index 44d4c5696..5f0045888 100644 --- a/core/io/io_uring.rs +++ b/core/io/io_uring.rs @@ -45,7 +45,7 @@ unsafe impl Sync for UringIO {} struct WrappedIOUring { ring: io_uring::IoUring, pending_ops: usize, - pub pending: [Option; MAX_IOVECS as usize + 1], + pub pending: [Option>; MAX_IOVECS as usize + 1], key: u64, } @@ -169,6 +169,13 @@ impl IO for UringIO { Ok(uring_file) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn run_once(&self) -> Result<()> { trace!("run_once()"); let mut inner = self.inner.borrow_mut(); @@ -298,16 +305,16 @@ impl File for UringFile { }; io.ring.submit_entry( &write, - Completion::Write(WriteCompletion::new(Box::new(move |result| { + Arc::new(Completion::Write(WriteCompletion::new(Box::new(move |result| { c.complete(result); // NOTE: Explicitly reference buffer to ensure it lives until here let _ = buffer.borrow(); - }))), + })))), ); Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { let fd = io_uring::types::Fd(self.file.as_raw_fd()); let mut io = self.io.borrow_mut(); trace!("sync()"); diff --git a/core/io/memory.rs b/core/io/memory.rs index cb20c6e63..415da8ca3 100644 --- a/core/io/memory.rs +++ b/core/io/memory.rs @@ -53,6 +53,10 @@ impl IO for MemoryIO { Ok(()) } + fn wait_for_completion(&self, _c: Arc) -> Result<()> { + todo!(); + } + fn generate_random_number(&self) -> i64 { let mut buf = [0u8; 8]; getrandom::getrandom(&mut buf).unwrap(); diff --git a/core/io/mod.rs b/core/io/mod.rs index 4cbe785c5..9c5f6d9c2 100644 --- a/core/io/mod.rs +++ b/core/io/mod.rs @@ -42,6 +42,8 @@ pub trait IO: Clock + Send + Sync { fn run_once(&self) -> Result<()>; + fn wait_for_completion(&self, c: Arc) -> Result<()>; + fn generate_random_number(&self) -> i64; fn get_memory_io(&self) -> Arc; @@ -60,9 +62,18 @@ pub enum Completion { pub struct ReadCompletion { pub buf: Arc>, pub complete: Box, + pub is_completed: RefCell, } impl Completion { + pub fn is_completed(&self) -> bool { + match self { + Self::Read(r) => *r.is_completed.borrow(), + Self::Write(w) => *w.is_completed.borrow(), + Self::Sync(s) => *s.is_completed.borrow(), + } + } + pub fn complete(&self, result: i32) { match self { Self::Read(r) => r.complete(), @@ -83,15 +94,21 @@ impl Completion { pub struct WriteCompletion { pub complete: Box, + pub is_completed: RefCell, } pub struct SyncCompletion { pub complete: Box, + pub is_completed: RefCell, } impl ReadCompletion { pub fn new(buf: Arc>, complete: Box) -> Self { - Self { buf, complete } + Self { + buf, + complete, + is_completed: RefCell::new(false), + } } pub fn buf(&self) -> Ref<'_, Buffer> { @@ -104,26 +121,35 @@ impl ReadCompletion { pub fn complete(&self) { (self.complete)(self.buf.clone()); + *self.is_completed.borrow_mut() = true; } } impl WriteCompletion { pub fn new(complete: Box) -> Self { - Self { complete } + Self { + complete, + is_completed: RefCell::new(false), + } } pub fn complete(&self, bytes_written: i32) { (self.complete)(bytes_written); + *self.is_completed.borrow_mut() = true; } } impl SyncCompletion { pub fn new(complete: Box) -> Self { - Self { complete } + Self { + complete, + is_completed: RefCell::new(false), + } } pub fn complete(&self, res: i32) { (self.complete)(res); + *self.is_completed.borrow_mut() = true; } } diff --git a/core/io/unix.rs b/core/io/unix.rs index 8c2f121e3..6772d6e45 100644 --- a/core/io/unix.rs +++ b/core/io/unix.rs @@ -256,6 +256,13 @@ impl IO for UnixIO { Ok(()) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn generate_random_number(&self) -> i64 { let mut buf = [0u8; 8]; getrandom::getrandom(&mut buf).unwrap(); diff --git a/core/io/vfs.rs b/core/io/vfs.rs index 7e953ebd0..7aae691e6 100644 --- a/core/io/vfs.rs +++ b/core/io/vfs.rs @@ -43,6 +43,10 @@ impl IO for VfsMod { Ok(()) } + fn wait_for_completion(&self, _c: Arc) -> Result<()> { + todo!(); + } + fn generate_random_number(&self) -> i64 { if self.ctx.is_null() { return -1; diff --git a/core/io/windows.rs b/core/io/windows.rs index a329abc14..dfa4dc241 100644 --- a/core/io/windows.rs +++ b/core/io/windows.rs @@ -33,6 +33,13 @@ impl IO for WindowsIO { })) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn run_once(&self) -> Result<()> { Ok(()) } @@ -74,7 +81,7 @@ impl File for WindowsFile { unimplemented!() } - fn pread(&self, pos: usize, c: Completion) -> Result<()> { + fn pread(&self, pos: usize, c: Arc) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; { @@ -87,7 +94,7 @@ impl File for WindowsFile { Ok(()) } - fn pwrite(&self, pos: usize, buffer: Arc>, c: Completion) -> Result<()> { + fn pwrite(&self, pos: usize, buffer: Arc>, c: Arc) -> Result<()> { let mut file = self.file.borrow_mut(); file.seek(std::io::SeekFrom::Start(pos as u64))?; let buf = buffer.borrow(); @@ -97,7 +104,7 @@ impl File for WindowsFile { Ok(()) } - fn sync(&self, c: Completion) -> Result<()> { + fn sync(&self, c: Arc) -> Result<()> { let file = self.file.borrow_mut(); file.sync_all().map_err(LimboError::IOError)?; c.complete(0); diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index dcbed64d0..e7bb68b41 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -892,6 +892,7 @@ pub fn begin_sync(db_file: Arc, syncing: Rc>) complete: Box::new(move |_| { *syncing.borrow_mut() = false; }), + is_completed: RefCell::new(false), }); #[allow(clippy::arc_with_non_send_sync)] db_file.sync(Arc::new(completion))?; diff --git a/core/storage/wal.rs b/core/storage/wal.rs index 4eab23e44..c929d69e3 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -751,6 +751,7 @@ impl Wal for WalFile { debug!("wal_sync finish"); *syncing.borrow_mut() = false; }), + is_completed: RefCell::new(false), }); shared.file.sync(Arc::new(completion))?; } diff --git a/simulator/runner/io.rs b/simulator/runner/io.rs index c775b3f9e..634873453 100644 --- a/simulator/runner/io.rs +++ b/simulator/runner/io.rs @@ -83,6 +83,13 @@ impl IO for SimulatorIO { Ok(file) } + fn wait_for_completion(&self, c: Arc) -> Result<()> { + while !c.is_completed() { + self.run_once()?; + } + Ok(()) + } + fn run_once(&self) -> Result<()> { if *self.fault.borrow() { *self.nr_run_once_faults.borrow_mut() += 1; From 3250560eb833c1fa18d79712423b12cec0a00fe8 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 27 May 2025 13:47:19 +0300 Subject: [PATCH 4/7] sqlite3: Add libsql_wal_get_frame() API --- core/lib.rs | 9 ++++++ core/storage/pager.rs | 16 +++++++++ core/storage/sqlite3_ondisk.rs | 6 ++-- core/storage/wal.rs | 45 ++++++++++++++++++++++++++ sqlite3/src/lib.rs | 39 ++++++++++++++++++++++ sqlite3/tests/compat/mod.rs | 59 +++++++++++++++++++++++++++++++++- 6 files changed, 170 insertions(+), 4 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index e60d74502..7e18b91ec 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -507,6 +507,15 @@ impl Connection { self.pager.wal_frame_count() } + pub fn wal_get_frame( + &self, + frame_no: u32, + p_frame: *mut u8, + frame_len: u32, + ) -> Result> { + self.pager.wal_get_frame(frame_no, p_frame, frame_len) + } + /// Flush dirty pages to disk. /// This will write the dirty pages to the WAL and then fsync the WAL. /// If the WAL size is over the checkpoint threshold, it will checkpoint the WAL to diff --git a/core/storage/pager.rs b/core/storage/pager.rs index d297c2692..204d99d17 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -5,6 +5,7 @@ use crate::storage::buffer_pool::BufferPool; use crate::storage::database::DatabaseStorage; use crate::storage::sqlite3_ondisk::{self, DatabaseHeader, PageContent, PageType}; use crate::storage::wal::{CheckpointResult, Wal, WalFsyncStatus}; +use crate::Completion; use crate::{Buffer, LimboError, Result}; use parking_lot::RwLock; use std::cell::{RefCell, UnsafeCell}; @@ -486,6 +487,21 @@ impl Pager { )) } + pub fn wal_get_frame( + &self, + frame_no: u32, + p_frame: *mut u8, + frame_len: u32, + ) -> Result> { + let wal = self.wal.borrow(); + return wal.read_frame_raw( + frame_no.into(), + self.buffer_pool.clone(), + p_frame, + frame_len, + ); + } + pub fn checkpoint(&self) -> Result { let mut checkpoint_result = CheckpointResult::default(); loop { diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index e7bb68b41..05d2bd62b 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -1535,7 +1535,7 @@ pub fn begin_read_wal_frame( offset: usize, buffer_pool: Rc, complete: Box>) -> ()>, -) -> Result<()> { +) -> Result> { trace!("begin_read_wal_frame(offset={})", offset); let buf = buffer_pool.get(); let drop_fn = Rc::new(move |buf| { @@ -1545,8 +1545,8 @@ pub fn begin_read_wal_frame( let buf = Arc::new(RefCell::new(Buffer::new(buf, drop_fn))); #[allow(clippy::arc_with_non_send_sync)] let c = Arc::new(Completion::Read(ReadCompletion::new(buf, complete))); - io.pread(offset, c)?; - Ok(()) + io.pread(offset, c.clone())?; + Ok(c) } pub fn begin_write_wal_frame( diff --git a/core/storage/wal.rs b/core/storage/wal.rs index c929d69e3..ca1950a25 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -1,4 +1,5 @@ #![allow(clippy::arc_with_non_send_sync)] +#![allow(clippy::not_unsafe_ptr_arg_deref)] use std::cell::UnsafeCell; use std::collections::HashMap; @@ -178,6 +179,15 @@ pub trait Wal { /// Read a frame from the WAL. fn read_frame(&self, frame_id: u64, page: PageRef, buffer_pool: Rc) -> Result<()>; + /// Read a frame from the WAL. + fn read_frame_raw( + &self, + frame_id: u64, + buffer_pool: Rc, + frame: *mut u8, + frame_len: u32, + ) -> Result>; + /// Write a frame to the WAL. fn append_frame( &mut self, @@ -235,6 +245,16 @@ impl Wal for DummyWAL { Ok(()) } + fn read_frame_raw( + &self, + _frame_id: u64, + _buffer_pool: Rc, + _frame: *mut u8, + _frame_len: u32, + ) -> Result> { + todo!(); + } + fn append_frame( &mut self, _page: crate::PageRef, @@ -537,6 +557,31 @@ impl Wal for WalFile { Ok(()) } + fn read_frame_raw( + &self, + frame_id: u64, + buffer_pool: Rc, + frame: *mut u8, + frame_len: u32, + ) -> Result> { + debug!("read_frame({})", frame_id); + let offset = self.frame_offset(frame_id); + let complete = Box::new(move |buf: Arc>| { + let buf = buf.borrow(); + let buf_ptr = buf.as_ptr(); + unsafe { + std::ptr::copy_nonoverlapping(buf_ptr, frame, frame_len as usize); + } + }); + let c = begin_read_wal_frame( + &self.get_shared().file, + offset + WAL_FRAME_HEADER_SIZE, + buffer_pool, + complete, + )?; + Ok(c) + } + /// Write a frame to the WAL. fn append_frame( &mut self, diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 188a06b39..8066bd7d5 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -1132,3 +1132,42 @@ pub unsafe extern "C" fn libsql_wal_frame_count( *p_frame_count = frame_count; SQLITE_OK } + +/// Get a frame from the WAL file +/// +/// The `libsql_wal_get_frame` function extracts frame `frame_no` from +/// the WAL for database connection `db` into memory pointed to by `p_frame` +/// of size `frame_len`. +/// +/// # Returns +/// +/// - `SQLITE_OK` if the frame is successfully returned. +/// - `SQLITE_MISUSE` if the `db` is `NULL`. +/// - `SQLITE_ERROR` if an error occurs while getting the frame. +/// +/// # Safety +/// +/// - The `db` must be a valid pointer to a `sqlite3` database connection. +/// - The `frame_no` must be a valid frame index. +/// - The `p_frame` must be a valid pointer to a `u8` that will store +/// the frame data. +/// - The `frame_len` must be the size of the frame. +#[no_mangle] +pub unsafe extern "C" fn libsql_wal_get_frame( + db: *mut sqlite3, + frame_no: u32, + p_frame: *mut u8, + frame_len: u32, +) -> ffi::c_int { + if db.is_null() { + return SQLITE_MISUSE; + } + let db: &mut sqlite3 = &mut *db; + match db.conn.wal_get_frame(frame_no, p_frame, frame_len) { + Ok(c) => match db.io.wait_for_completion(c) { + Ok(_) => SQLITE_OK, + Err(_) => SQLITE_ERROR, + }, + Err(_) => SQLITE_ERROR, + } +} diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index ed1145713..3213fbea1 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -38,6 +38,12 @@ extern "C" { checkpoint_count: *mut i32, ) -> i32; fn libsql_wal_frame_count(db: *mut sqlite3, p_frame_count: *mut u32) -> i32; + fn libsql_wal_get_frame( + db: *mut sqlite3, + frame_no: u32, + p_frame: *mut u8, + frame_len: u32, + ) -> i32; } const SQLITE_OK: i32 = 0; @@ -204,7 +210,7 @@ mod tests { unsafe { let mut db = ptr::null_mut(); assert_eq!( - sqlite3_open(c"../testing/testing_clone.db".as_ptr(), &mut db), + sqlite3_open(c"../testing/test_wal_frame_count.db".as_ptr(), &mut db), SQLITE_OK ); // Ensure that WAL is initially empty. @@ -244,5 +250,56 @@ mod tests { assert_eq!(sqlite3_close(db), SQLITE_OK); } } + + #[test] + fn test_read_frame() { + unsafe { + let mut db = ptr::null_mut(); + assert_eq!( + sqlite3_open(c"../testing/test_read_frame.db".as_ptr(), &mut db), + SQLITE_OK + ); + // Create a table and insert a row. + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"CREATE TABLE test (id INTEGER PRIMARY KEY)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + let mut stmt = ptr::null_mut(); + assert_eq!( + sqlite3_prepare_v2( + db, + c"INSERT INTO test (id) VALUES (1)".as_ptr(), + -1, + &mut stmt, + ptr::null_mut() + ), + SQLITE_OK + ); + assert_eq!(sqlite3_step(stmt), SQLITE_DONE); + assert_eq!(sqlite3_finalize(stmt), SQLITE_OK); + // Check that WAL has three frames. + let mut frame_count = 0; + assert_eq!(libsql_wal_frame_count(db, &mut frame_count), SQLITE_OK); + assert_eq!(frame_count, 3); + for i in 1..frame_count + 1 { + let frame_len = 4096 + 24; + let mut frame = vec![0; frame_len]; + assert_eq!( + libsql_wal_get_frame(db, i, frame.as_mut_ptr(), frame_len as u32), + SQLITE_OK + ); + } + assert_eq!(sqlite3_close(db), SQLITE_OK); + } + } } } From 59d28eac93695317a46ad1305ec0af14221725eb Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 27 May 2025 14:05:07 +0300 Subject: [PATCH 5/7] core: Switch Completion "is_completed" to use Cell Suggested by Jussi --- core/io/mod.rs | 26 +++++++++++++------------- core/storage/sqlite3_ondisk.rs | 4 ++-- core/storage/wal.rs | 9 +++++++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/core/io/mod.rs b/core/io/mod.rs index 9c5f6d9c2..73fec5246 100644 --- a/core/io/mod.rs +++ b/core/io/mod.rs @@ -4,7 +4,7 @@ use cfg_block::cfg_block; use std::fmt; use std::sync::Arc; use std::{ - cell::{Ref, RefCell, RefMut}, + cell::{Cell, Ref, RefCell, RefMut}, fmt::Debug, mem::ManuallyDrop, pin::Pin, @@ -62,15 +62,15 @@ pub enum Completion { pub struct ReadCompletion { pub buf: Arc>, pub complete: Box, - pub is_completed: RefCell, + pub is_completed: Cell, } impl Completion { pub fn is_completed(&self) -> bool { match self { - Self::Read(r) => *r.is_completed.borrow(), - Self::Write(w) => *w.is_completed.borrow(), - Self::Sync(s) => *s.is_completed.borrow(), + Self::Read(r) => r.is_completed.get(), + Self::Write(w) => w.is_completed.get(), + Self::Sync(s) => s.is_completed.get(), } } @@ -94,12 +94,12 @@ impl Completion { pub struct WriteCompletion { pub complete: Box, - pub is_completed: RefCell, + pub is_completed: Cell, } pub struct SyncCompletion { pub complete: Box, - pub is_completed: RefCell, + pub is_completed: Cell, } impl ReadCompletion { @@ -107,7 +107,7 @@ impl ReadCompletion { Self { buf, complete, - is_completed: RefCell::new(false), + is_completed: Cell::new(false), } } @@ -121,7 +121,7 @@ impl ReadCompletion { pub fn complete(&self) { (self.complete)(self.buf.clone()); - *self.is_completed.borrow_mut() = true; + self.is_completed.set(true); } } @@ -129,13 +129,13 @@ impl WriteCompletion { pub fn new(complete: Box) -> Self { Self { complete, - is_completed: RefCell::new(false), + is_completed: Cell::new(false), } } pub fn complete(&self, bytes_written: i32) { (self.complete)(bytes_written); - *self.is_completed.borrow_mut() = true; + self.is_completed.set(true); } } @@ -143,13 +143,13 @@ impl SyncCompletion { pub fn new(complete: Box) -> Self { Self { complete, - is_completed: RefCell::new(false), + is_completed: Cell::new(false), } } pub fn complete(&self, res: i32) { (self.complete)(res); - *self.is_completed.borrow_mut() = true; + self.is_completed.set(true); } } diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 05d2bd62b..86dff2001 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -53,7 +53,7 @@ use crate::types::{ ImmutableRecord, RawSlice, RefValue, SerialType, SerialTypeKind, TextRef, TextSubtype, }; use crate::{File, Result, WalFileShared}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::collections::HashMap; use std::mem::MaybeUninit; use std::pin::Pin; @@ -892,7 +892,7 @@ pub fn begin_sync(db_file: Arc, syncing: Rc>) complete: Box::new(move |_| { *syncing.borrow_mut() = false; }), - is_completed: RefCell::new(false), + is_completed: Cell::new(false), }); #[allow(clippy::arc_with_non_send_sync)] db_file.sync(Arc::new(completion))?; diff --git a/core/storage/wal.rs b/core/storage/wal.rs index ca1950a25..039d5d2e9 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -7,7 +7,12 @@ use tracing::{debug, trace}; use std::fmt::Formatter; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; -use std::{cell::RefCell, fmt, rc::Rc, sync::Arc}; +use std::{ + cell::{Cell, RefCell}, + fmt, + rc::Rc, + sync::Arc, +}; use crate::fast_lock::SpinLock; use crate::io::{File, SyncCompletion, IO}; @@ -796,7 +801,7 @@ impl Wal for WalFile { debug!("wal_sync finish"); *syncing.borrow_mut() = false; }), - is_completed: RefCell::new(false), + is_completed: Cell::new(false), }); shared.file.sync(Arc::new(completion))?; } From edfa7402f0d9e97abe561bc0e9c8eeef11d0cac8 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 27 May 2025 16:45:02 +0300 Subject: [PATCH 6/7] sqlite3/test: Use tempfile in read frame test case ...make test runs idempotent, as suggested by Jussi. --- Cargo.lock | 1 + sqlite3/Cargo.toml | 3 +++ sqlite3/tests/compat/mod.rs | 11 +++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9aad80c35..ef8415504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1977,6 +1977,7 @@ dependencies = [ "env_logger 0.11.7", "libc", "limbo_core", + "tempfile", "tracing", "tracing-appender", "tracing-subscriber", diff --git a/sqlite3/Cargo.toml b/sqlite3/Cargo.toml index 84b71b09f..bc6c63989 100644 --- a/sqlite3/Cargo.toml +++ b/sqlite3/Cargo.toml @@ -29,6 +29,9 @@ tracing = "0.1.41" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +[dev-dependencies] +tempfile = "3.8.0" + [package.metadata.capi.header] name = "sqlite3.h" diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 3213fbea1..28926dcce 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -1,6 +1,7 @@ #![allow(non_camel_case_types)] #![allow(dead_code)] +use std::ffi::CString; use std::ptr; #[repr(C)] @@ -203,6 +204,8 @@ mod tests { #[cfg(not(feature = "sqlite3"))] mod libsql_ext { + use libc::tm; + use super::*; #[test] @@ -255,10 +258,10 @@ mod tests { fn test_read_frame() { unsafe { let mut db = ptr::null_mut(); - assert_eq!( - sqlite3_open(c"../testing/test_read_frame.db".as_ptr(), &mut db), - SQLITE_OK - ); + let mut temp_file = tempfile::NamedTempFile::new().unwrap(); + let path = temp_file.path(); + let c_path = CString::new(path.to_str().unwrap()).unwrap(); + assert_eq!(sqlite3_open(c_path.as_ptr(), &mut db), SQLITE_OK); // Create a table and insert a row. let mut stmt = ptr::null_mut(); assert_eq!( From 99926c5f99269dba9210e25f7e1b61508e28d478 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 27 May 2025 18:16:54 +0300 Subject: [PATCH 7/7] sqlite3/tests: Clippy is not happy --- sqlite3/tests/compat/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sqlite3/tests/compat/mod.rs b/sqlite3/tests/compat/mod.rs index 28926dcce..1568b0b49 100644 --- a/sqlite3/tests/compat/mod.rs +++ b/sqlite3/tests/compat/mod.rs @@ -1,7 +1,6 @@ #![allow(non_camel_case_types)] #![allow(dead_code)] -use std::ffi::CString; use std::ptr; #[repr(C)]