diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..dfea145 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] limits = [] hooks = [] sqlcipher = ["libsqlite3-sys/sqlcipher"] +unlock_notify = ["libsqlite3-sys/unlock_notify"] [dependencies] time = "0.1.0" diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..d038713 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -21,6 +21,8 @@ min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_3 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] +# sqlite3_unlock_notify >= 3.6.12 +unlock_notify = [] [build-dependencies] bindgen = { version = "0.32", optional = true } diff --git a/libsqlite3-sys/build.rs b/libsqlite3-sys/build.rs index 0757d9f..955f565 100644 --- a/libsqlite3-sys/build.rs +++ b/libsqlite3-sys/build.rs @@ -18,8 +18,8 @@ mod build { fs::copy("sqlite3/bindgen_bundled_version.rs", out_path) .expect("Could not copy bindings to output directory"); - cc::Build::new() - .file("sqlite3/sqlite3.c") + let mut cfg = cc::Build::new(); + cfg.file("sqlite3/sqlite3.c") .flag("-DSQLITE_CORE") .flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1") .flag("-DSQLITE_ENABLE_API_ARMOR") @@ -38,8 +38,11 @@ mod build { .flag("-DSQLITE_SOUNDEX") .flag("-DSQLITE_THREADSAFE=1") .flag("-DSQLITE_USE_URI") - .flag("-DHAVE_USLEEP=1") - .compile("libsqlite3.a"); + .flag("-DHAVE_USLEEP=1"); + if cfg!(feature = "unlock_notify") { + cfg.flag("-DSQLITE_ENABLE_UNLOCK_NOTIFY"); + } + cfg.compile("libsqlite3.a"); } } diff --git a/src/lib.rs b/src/lib.rs index 0ccd22f..238ed09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,8 @@ pub mod limits; mod hooks; #[cfg(feature = "hooks")] pub use hooks::*; +#[cfg(feature = "unlock_notify")] +pub mod unlock_notify; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs new file mode 100644 index 0000000..ac0a2d3 --- /dev/null +++ b/src/unlock_notify.rs @@ -0,0 +1,80 @@ +//! [Unlock Notification](http://sqlite.org/unlock_notify.html) + +use std::sync::{Mutex, Condvar}; +use std::os::raw::{c_char, c_int, c_void}; + +use ffi; +use InnerConnection; + +struct UnlockNotification { + cond: Condvar, // Condition variable to wait on + mutex: Mutex, // Mutex to protect structure +} + +impl UnlockNotification { + fn new() -> UnlockNotification { + UnlockNotification { + cond: Condvar::new(), + mutex: Mutex::new(false), + } + } + + fn fired(&mut self) { + *self.mutex.lock().unwrap() = true; + self.cond.notify_one(); + } + + fn wait(&mut self) -> bool { + let mut fired = self.mutex.lock().unwrap(); + if !*fired { + fired = self.cond.wait(fired).unwrap(); + } + *fired + } +} + +/// This function is an unlock-notify callback +unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { + /*int i; + for(i=0; imutex); + p->fired = 1; + pthread_cond_signal(&p->cond); + pthread_mutex_unlock(&p->mutex); + }*/ +} + +impl InnerConnection { + fn blocking_prepare(&mut self, + z_sql: *const c_char, + n_byte: c_int, + pp_stmt: *mut *mut ffi::sqlite3_stmt, + pz_tail: *mut *const c_char) -> c_int { + let mut rc; + loop { + rc = unsafe { + ffi::sqlite3_prepare_v2(self.db, z_sql, n_byte, pp_stmt, pz_tail) + }; + if rc != ffi::SQLITE_LOCKED { + break; + } + rc = self.wait_for_unlock_notify(); + if rc != ffi::SQLITE_OK { + break; + } + } + rc + } + + fn wait_for_unlock_notify(&mut self) -> c_int { + let mut un = UnlockNotification::new(); + /* Register for an unlock-notify callback. */ + let rc = unsafe { ffi::sqlite3_unlock_notify(self.db, Some(unlock_notify_cb), &mut un as *mut UnlockNotification as *mut c_void) }; + debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); + if rc == ffi::SQLITE_OK { + un.wait(); + } + rc + } +} \ No newline at end of file