From dc81f78c20632463114ec0e422b2c5242a1b4b77 Mon Sep 17 00:00:00 2001 From: SAU-249 implementer Date: Wed, 28 Oct 2026 00:33:33 +0000 Subject: [PATCH] Add shared SQLite open macro with WASI crash recovery and in-memory temp storage Card: SAU-249 --- sdk/src/lib.rs | 1 + sdk/src/sqlite.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 sdk/src/sqlite.rs diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 3924382..c8e2d44 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -392,6 +392,7 @@ mod error; mod helpers; mod imports; mod lines; +mod sqlite; mod transport; pub use discovery::*; pub use endpoints::*; diff --git a/sdk/src/sqlite.rs b/sdk/src/sqlite.rs new file mode 100644 index 0000000..981bec9 --- /dev/null +++ b/sdk/src/sqlite.rs @@ -0,0 +1,43 @@ +//! Shared SQLite open policy, expanded against the plugin's own rusqlite version. + +/// Open SQLite under an already-held Sausage host lock. The caller must depend +/// on `rusqlite` (bundled) and hold the SAME host lock for EVERY connection's +/// entire lifetime, including reads and old/new plugin versions. This macro does +/// not acquire the lock. Do not use it for the host journal or databases accessed +/// outside Sausage. Native builds retain OS locks; WASI selects `unix-none`, so +/// crash-leftover dotfile lock directories cannot block recovery. +/// +/// Temporary tables/sorts stay in memory before any schema access. Writable opens +/// use DELETE journals; SQLite rolls back hot journals itself. Never delete a +/// leftover journal. Read-only opens cannot recover a hot journal: recover with +/// a writable open first. +/// +/// The flags form preserves the caller's open flags. A macro avoids coupling the +/// SDK to a particular rusqlite/libsqlite3-sys version (the host and plugins use +/// different versions, which cannot coexist as dependencies due to `links`). +/// +/// ```ignore +/// let _guard = sausage_plugin::lock("/state/example/db.lock").await?; +/// let c = sausage_plugin::sqlite_open!("/state/example/db.sqlite")?; +/// // Query here; c must drop before _guard. +/// ``` +#[macro_export] +macro_rules! sqlite_open { + ($path:expr) => { + $crate::sqlite_open!($path, ::rusqlite::OpenFlags::default()) + }; + ($path:expr, $flags:expr) => {{ + (|| -> ::rusqlite::Result<::rusqlite::Connection> { + let flags = $flags; + #[cfg(target_os = "wasi")] + let c = ::rusqlite::Connection::open_with_flags_and_vfs($path, flags, "unix-none")?; + #[cfg(not(target_os = "wasi"))] + let c = ::rusqlite::Connection::open_with_flags($path, flags)?; + c.pragma_update(None, "temp_store", "MEMORY")?; + if !flags.contains(::rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY) { + c.pragma_update(None, "journal_mode", "DELETE")?; + } + Ok(c) + })() + }}; +}