From 3b575c3b4ae3e64bcb71f34e373fd156e15b6b1e Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 25 Apr 2017 20:58:22 +0200 Subject: [PATCH 01/28] Implementation of sqlite3_update_hook #260 First draft (no tested and a memory leak) --- .travis.yml | 9 +-- Cargo.toml | 1 + appveyor.yml | 8 +-- src/hooks.rs | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 5 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 src/hooks.rs diff --git a/.travis.yml b/.travis.yml index dd9573f..d6a8990 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,13 +30,14 @@ script: - cargo test --features backup - cargo test --features blob - cargo test --features functions + - cargo test --features hooks - cargo test --features limits - cargo test --features load_extension - cargo test --features trace - cargo test --features chrono - cargo test --features serde_json - cargo test --features bundled - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" diff --git a/Cargo.toml b/Cargo.toml index 6e9fd53..65aa744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"] bundled = ["libsqlite3-sys/bundled"] buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] limits = [] +hooks = [] [dependencies] time = "0.1.0" diff --git a/appveyor.yml b/appveyor.yml index 2d7a1c3..d7801eb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,10 +20,10 @@ build: false test_script: - cargo test --lib --verbose - cargo test --lib --verbose --features bundled - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" cache: - C:\Users\appveyor\.cargo diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 0000000..88b08d8 --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,158 @@ +//! Data Change Notification Callbacks +#![allow(non_camel_case_types)] + +use std::mem; +use std::ptr; +use std::os::raw::{c_int, c_char, c_void}; + +use ffi; + +use {Connection, InnerConnection}; + +// Commit And Rollback Notification Callbacks +// http://sqlite.org/c3ref/commit_hook.html +/* +void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +*/ + +/// Authorizer Action Codes +pub enum Action { + UNKNOWN = -1, + SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize, + SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize, + SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize, + SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize, + SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize, + SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize, + SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize, + SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize, + SQLITE_DELETE = ffi::SQLITE_DELETE as isize, + SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize, + SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize, + SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize, + SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize, + SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize, + SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize, + SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize, + SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize, + SQLITE_INSERT = ffi::SQLITE_INSERT as isize, + SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize, + SQLITE_READ = ffi::SQLITE_READ as isize, + SQLITE_SELECT = ffi::SQLITE_SELECT as isize, + SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize, + SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize, + SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize, + SQLITE_DETACH = ffi::SQLITE_DETACH as isize, + SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize, + SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize, + SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize, + SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize, + SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize, + SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize, + SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize, + SQLITE_COPY = ffi::SQLITE_COPY as isize, + SQLITE_RECURSIVE = ffi::SQLITE_RECURSIVE as isize, +} + +impl From for Action { + fn from(code: i32) -> Action { + match code { + ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX, + ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE, + ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX, + ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE, + ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER, + ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW, + ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER, + ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW, + ffi::SQLITE_DELETE => Action::SQLITE_DELETE, + ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX, + ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE, + ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX, + ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE, + ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER, + ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW, + ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER, + ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW, + ffi::SQLITE_INSERT => Action::SQLITE_INSERT, + ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA, + ffi::SQLITE_READ => Action::SQLITE_READ, + ffi::SQLITE_SELECT => Action::SQLITE_SELECT, + ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION, + ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE, + ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH, + ffi::SQLITE_DETACH => Action::SQLITE_DETACH, + ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE, + ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX, + ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE, + ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE, + ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE, + ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION, + ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT, + ffi::SQLITE_COPY => Action::SQLITE_COPY, + ffi::SQLITE_RECURSIVE => Action::SQLITE_RECURSIVE, + _ => Action::UNKNOWN, + } + } +} + +impl Connection { + pub fn update_hook(&self, hook: Option) + where F: FnMut(Action, &str, &str, i64) + { + self.db.borrow_mut().update_hook(hook); + } +} + +impl InnerConnection { + // TODO self.update_hook(None) must be called in InnerConnection#close to free any hook + fn update_hook(&mut self, hook: Option) + where F: FnMut(Action, &str, &str, i64) + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void, + action_code: c_int, + db_str: *const c_char, + tbl_str: *const c_char, + row_id: i64) + where F: FnMut(Action, &str, &str, i64) + { + use std::ffi::CStr; + use std::str; + + let boxed_hook: *mut F = mem::transmute(p_arg); + assert!(!boxed_hook.is_null(), + "Internal error - null function pointer"); + + let action = Action::from(action_code); + let db_name = { + let c_slice = CStr::from_ptr(db_str).to_bytes(); + str::from_utf8_unchecked(c_slice) + }; + let tbl_name = { + let c_slice = CStr::from_ptr(tbl_str).to_bytes(); + str::from_utf8_unchecked(c_slice) + }; + + (*boxed_hook)(action, db_name, tbl_name, row_id); + } + + let previous_hook = if let Some(hook) = hook { + let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); + unsafe { + ffi::sqlite3_update_hook(self.db(), + Some(call_boxed_closure::), + mem::transmute(boxed_hook)) + } + } else { + unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) } + }; + // TODO Validate: what happens if the previous hook has been set from C ? + if !previous_hook.is_null() { + // free_boxed_value + unsafe { + let _: Box = Box::from_raw(mem::transmute(previous_hook)); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9c0f45a..4e0d741 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,6 +121,8 @@ pub mod functions; pub mod blob; #[cfg(feature = "limits")] pub mod limits; +#[cfg(feature = "hooks")] +pub mod hooks; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; From 6eb98afd67c4f2c4a82973a37e56d54c73cdcede Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 25 Apr 2017 21:08:41 +0200 Subject: [PATCH 02/28] SQLITE_RECURSIVE is not available with SQLite 3.6.8 --- src/hooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks.rs b/src/hooks.rs index 88b08d8..7b5fee9 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -52,7 +52,7 @@ pub enum Action { SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize, SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize, SQLITE_COPY = ffi::SQLITE_COPY as isize, - SQLITE_RECURSIVE = ffi::SQLITE_RECURSIVE as isize, + SQLITE_RECURSIVE = 33, } impl From for Action { From 63a444a95f5b8345ecb68c5bbb83cb0d929c736c Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 26 Apr 2017 20:12:48 +0200 Subject: [PATCH 03/28] Fix memory leak and add test --- src/hooks.rs | 82 +++++++++++++++++++++++++++++++++++++++------------- src/lib.rs | 5 ++++ 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 7b5fee9..42f88b6 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -16,7 +16,9 @@ void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); */ + /// Authorizer Action Codes +#[derive(Debug, PartialEq)] pub enum Action { UNKNOWN = -1, SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize, @@ -91,23 +93,31 @@ impl From for Action { ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION, ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT, ffi::SQLITE_COPY => Action::SQLITE_COPY, - ffi::SQLITE_RECURSIVE => Action::SQLITE_RECURSIVE, + 33 => Action::SQLITE_RECURSIVE, _ => Action::UNKNOWN, } } } impl Connection { - pub fn update_hook(&self, hook: Option) + /// Register a callback function to be invoked whenever a row is updated, inserted or deleted in a rowid table. + pub fn update_hook(&self, hook: F) where F: FnMut(Action, &str, &str, i64) { self.db.borrow_mut().update_hook(hook); } + + pub fn remove_update_hook(&self) { + self.db.borrow_mut().remove_update_hook(); + } } impl InnerConnection { - // TODO self.update_hook(None) must be called in InnerConnection#close to free any hook - fn update_hook(&mut self, hook: Option) + pub fn remove_hooks(&mut self) { + self.remove_update_hook(); + } + + fn update_hook(&mut self, hook: F) where F: FnMut(Action, &str, &str, i64) { unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void, @@ -120,9 +130,7 @@ impl InnerConnection { use std::ffi::CStr; use std::str; - let boxed_hook: *mut F = mem::transmute(p_arg); - assert!(!boxed_hook.is_null(), - "Internal error - null function pointer"); + let boxed_hook: &mut Box = mem::transmute(p_arg); let action = Action::from(action_code); let db_name = { @@ -134,25 +142,59 @@ impl InnerConnection { str::from_utf8_unchecked(c_slice) }; - (*boxed_hook)(action, db_name, tbl_name, row_id); + boxed_hook(action, db_name, tbl_name, row_id); } - let previous_hook = if let Some(hook) = hook { - let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); + let previous_hook = { + let boxed_hook: Box> = Box::new(Box::new(hook)); unsafe { ffi::sqlite3_update_hook(self.db(), Some(call_boxed_closure::), - mem::transmute(boxed_hook)) + Box::into_raw(boxed_hook) as *mut _) } - } else { - unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) } }; - // TODO Validate: what happens if the previous hook has been set from C ? - if !previous_hook.is_null() { - // free_boxed_value - unsafe { - let _: Box = Box::from_raw(mem::transmute(previous_hook)); - } - } + free_boxed_update_hook(previous_hook); + } + + fn remove_update_hook(&mut self) { + let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) }; + free_boxed_update_hook(previous_hook); + } + +} + +fn free_boxed_update_hook(hook: *mut c_void) { + /* + http://stackoverflow.com/questions/32270030/how-do-i-convert-a-rust-closure-to-a-c-style-callback + Double indirection (i.e. Box>) is necessary + because Box ..> is a trait object and therefore a fat pointer, + incompatible with *mut c_void because of different size. + */ + if !hook.is_null() { + let _: Box> = unsafe { Box::from_raw(hook as *mut _) }; + } +} + +#[cfg(test)] +mod test { + use super::Action; + use Connection; + + #[test] + fn test_update_hook() { + let db = Connection::open_in_memory().unwrap(); + + let mut called = false; + db.update_hook(|action, db, tbl, row_id| { + assert_eq!(Action::SQLITE_INSERT, action); + assert_eq!("main", db); + assert_eq!("foo", tbl); + assert_eq!(1, row_id); + called = true; + }); + db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap(); + db.execute_batch("INSERT INTO foo VALUES ('lisa')") + .unwrap(); + assert!(called); } } diff --git a/src/lib.rs b/src/lib.rs index 4e0d741..a55cd9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -781,6 +781,7 @@ impl InnerConnection { } fn close(&mut self) -> Result<()> { + self.remove_hooks(); unsafe { let r = ffi::sqlite3_close(self.db()); let r = self.decode_result(r); @@ -857,6 +858,10 @@ impl InnerConnection { fn changes(&mut self) -> c_int { unsafe { ffi::sqlite3_changes(self.db()) } } + + #[cfg(not(feature = "hooks"))] + fn remove_hooks(&mut self) { + } } impl Drop for InnerConnection { From 3e6fffaf94e181ddb93ada2ed1961bf191b836db Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 27 Apr 2017 18:05:12 +0200 Subject: [PATCH 04/28] Due to rust monophormisation, there is no need for double indirection --- src/blob.rs | 5 ++--- src/hooks.rs | 18 +++++++----------- src/lib.rs | 3 +++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index 4361366..06457d6 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -50,7 +50,6 @@ //! ``` use std::io; use std::cmp::min; -use std::mem; use std::ptr; use super::ffi; @@ -155,7 +154,7 @@ impl<'conn> io::Read for Blob<'conn> { return Ok(0); } let rc = - unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) }; + unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) }; self.conn .decode_result(rc) .map(|_| { @@ -184,7 +183,7 @@ impl<'conn> io::Write for Blob<'conn> { return Ok(0); } let rc = unsafe { - ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) + ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos) }; self.conn .decode_result(rc) diff --git a/src/hooks.rs b/src/hooks.rs index 42f88b6..0f3b359 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -130,7 +130,8 @@ impl InnerConnection { use std::ffi::CStr; use std::str; - let boxed_hook: &mut Box = mem::transmute(p_arg); + let boxed_hook: *mut F = mem::transmute(p_arg); + assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); let action = Action::from(action_code); let db_name = { @@ -142,15 +143,15 @@ impl InnerConnection { str::from_utf8_unchecked(c_slice) }; - boxed_hook(action, db_name, tbl_name, row_id); + (*boxed_hook)(action, db_name, tbl_name, row_id); } let previous_hook = { - let boxed_hook: Box> = Box::new(Box::new(hook)); + let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); unsafe { ffi::sqlite3_update_hook(self.db(), Some(call_boxed_closure::), - Box::into_raw(boxed_hook) as *mut _) + boxed_hook as *mut _) } }; free_boxed_update_hook(previous_hook); @@ -164,14 +165,9 @@ impl InnerConnection { } fn free_boxed_update_hook(hook: *mut c_void) { - /* - http://stackoverflow.com/questions/32270030/how-do-i-convert-a-rust-closure-to-a-c-style-callback - Double indirection (i.e. Box>) is necessary - because Box ..> is a trait object and therefore a fat pointer, - incompatible with *mut c_void because of different size. - */ if !hook.is_null() { - let _: Box> = unsafe { Box::from_raw(hook as *mut _) }; + // TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>() + let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) }; } } diff --git a/src/lib.rs b/src/lib.rs index a55cd9b..949271a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -781,6 +781,9 @@ impl InnerConnection { } fn close(&mut self) -> Result<()> { + if self.db.is_null() { + return Ok(()); + } self.remove_hooks(); unsafe { let r = ffi::sqlite3_close(self.db()); From 466b8aab2fdaa3352ac605ade4d8eb5e4e1047b4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 12 May 2017 19:12:10 +0200 Subject: [PATCH 05/28] Rename `row` parameter to `row_id` --- src/blob.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index 06457d6..85364a6 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -64,7 +64,7 @@ pub struct Blob<'conn> { } impl Connection { - /// Open a handle to the BLOB located in `row`, `column`, `table` in database `db`. + /// Open a handle to the BLOB located in `row_id`, `column`, `table` in database `db`. /// /// # Failure /// @@ -74,7 +74,7 @@ impl Connection { db: DatabaseName, table: &str, column: &str, - row: i64, + row_id: i64, read_only: bool) -> Result> { let mut c = self.db.borrow_mut(); @@ -87,7 +87,7 @@ impl Connection { db.as_ptr(), table.as_ptr(), column.as_ptr(), - row, + row_id, if read_only { 0 } else { 1 }, &mut blob) }; From dfdd42fd09474686402ce12bba95205d35ddd164 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 12 May 2017 19:14:34 +0200 Subject: [PATCH 06/28] Bind sqlite3_commit_hook and sqlite3_rollback_hook --- src/hooks.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 4 +- 2 files changed, 125 insertions(+), 13 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 0f3b359..3c6bb0b 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,4 +1,4 @@ -//! Data Change Notification Callbacks +//! Commit, Data Change and Rollback Notification Callbacks #![allow(non_camel_case_types)] use std::mem; @@ -9,14 +9,6 @@ use ffi; use {Connection, InnerConnection}; -// Commit And Rollback Notification Callbacks -// http://sqlite.org/c3ref/commit_hook.html -/* -void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); -void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); -*/ - - /// Authorizer Action Codes #[derive(Debug, PartialEq)] pub enum Action { @@ -100,21 +92,105 @@ impl From for Action { } impl Connection { + /// Register a callback function to be invoked whenever a transaction is committed. + /// + /// The callback returns `true` to rollback. + pub fn commit_hook(&self, hook: F) + where F: FnMut() -> bool + { + self.db.borrow_mut().commit_hook(hook); + } + + /// Register a callback function to be invoked whenever a transaction is committed. + /// + /// The callback returns `true` to rollback. + pub fn rollback_hook(&self, hook: F) + where F: FnMut() + { + self.db.borrow_mut().rollback_hook(hook); + } + /// Register a callback function to be invoked whenever a row is updated, inserted or deleted in a rowid table. + /// + /// The callback parameters are: + /// + /// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE), + /// - the name of the database ("main", "temp", ...), + /// - the name of the table that is updated, + /// - the ROWID of the row that is updated. pub fn update_hook(&self, hook: F) where F: FnMut(Action, &str, &str, i64) { self.db.borrow_mut().update_hook(hook); } + /// Remove hook installed by `update_hook`. pub fn remove_update_hook(&self) { self.db.borrow_mut().remove_update_hook(); } + + /// Remove hook installed by `commit_hook`. + pub fn remove_commit_hook(&self) { + self.db.borrow_mut().remove_commit_hook(); + } + + /// Remove hook installed by `rollback_hook`. + pub fn remove_rollback_hook(&self) { + self.db.borrow_mut().remove_rollback_hook(); + } } impl InnerConnection { pub fn remove_hooks(&mut self) { self.remove_update_hook(); + self.remove_commit_hook(); + self.remove_rollback_hook(); + } + + fn commit_hook(&self, hook: F) + where F: FnMut() -> bool + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) -> c_int + where F: FnMut() -> bool + { + let boxed_hook: *mut F = mem::transmute(p_arg); + assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); + + if (*boxed_hook)() { 1 } else { 0 } + } + + let previous_hook = { + let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); + unsafe { + ffi::sqlite3_commit_hook(self.db(), + Some(call_boxed_closure::), + boxed_hook as *mut _) + } + }; + free_boxed_hook(previous_hook); + } + + fn rollback_hook(&self, hook: F) + where F: FnMut() + { + unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) + where F: FnMut() + { + let boxed_hook: *mut F = mem::transmute(p_arg); + assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); + + (*boxed_hook)(); + } + + let previous_hook = { + let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); + unsafe { + ffi::sqlite3_rollback_hook(self.db(), + Some(call_boxed_closure::), + boxed_hook as *mut _) + } + }; + free_boxed_hook(previous_hook); } fn update_hook(&mut self, hook: F) @@ -154,17 +230,26 @@ impl InnerConnection { boxed_hook as *mut _) } }; - free_boxed_update_hook(previous_hook); + free_boxed_hook(previous_hook); } fn remove_update_hook(&mut self) { let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) }; - free_boxed_update_hook(previous_hook); + free_boxed_hook(previous_hook); } + fn remove_commit_hook(&mut self) { + let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) }; + free_boxed_hook(previous_hook); + } + + fn remove_rollback_hook(&mut self) { + let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) }; + free_boxed_hook(previous_hook); + } } -fn free_boxed_update_hook(hook: *mut c_void) { +fn free_boxed_hook(hook: *mut c_void) { if !hook.is_null() { // TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>() let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) }; @@ -176,6 +261,31 @@ mod test { use super::Action; use Connection; + #[test] + fn test_commit_hook() { + let db = Connection::open_in_memory().unwrap(); + + let mut called = false; + db.commit_hook(|| { + called = true; + false + }); + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;").unwrap(); + assert!(called); + } + + #[test] + fn test_rollback_hook() { + let db = Connection::open_in_memory().unwrap(); + + let mut called = false; + db.rollback_hook(|| { + called = true; + }); + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;").unwrap(); + assert!(called); + } + #[test] fn test_update_hook() { let db = Connection::open_in_memory().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 949271a..1b568a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,7 +122,9 @@ pub mod blob; #[cfg(feature = "limits")] pub mod limits; #[cfg(feature = "hooks")] -pub mod hooks; +mod hooks; +#[cfg(feature = "hooks")] +pub use hooks::*; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; From 94670c0119baca5fc55c40f5a9820910787d9dba Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 12 May 2017 19:18:42 +0200 Subject: [PATCH 07/28] Rustfmt --- src/hooks.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/hooks.rs b/src/hooks.rs index 3c6bb0b..4bf0a59 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -110,7 +110,8 @@ impl Connection { self.db.borrow_mut().rollback_hook(hook); } - /// Register a callback function to be invoked whenever a row is updated, inserted or deleted in a rowid table. + /// Register a callback function to be invoked whenever a row is updated, + /// inserted or deleted in a rowid table. /// /// The callback parameters are: /// @@ -154,7 +155,8 @@ impl InnerConnection { where F: FnMut() -> bool { let boxed_hook: *mut F = mem::transmute(p_arg); - assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); + assert!(!boxed_hook.is_null(), + "Internal error - null function pointer"); if (*boxed_hook)() { 1 } else { 0 } } @@ -177,7 +179,8 @@ impl InnerConnection { where F: FnMut() { let boxed_hook: *mut F = mem::transmute(p_arg); - assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); + assert!(!boxed_hook.is_null(), + "Internal error - null function pointer"); (*boxed_hook)(); } @@ -186,8 +189,8 @@ impl InnerConnection { let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); unsafe { ffi::sqlite3_rollback_hook(self.db(), - Some(call_boxed_closure::), - boxed_hook as *mut _) + Some(call_boxed_closure::), + boxed_hook as *mut _) } }; free_boxed_hook(previous_hook); @@ -207,7 +210,8 @@ impl InnerConnection { use std::str; let boxed_hook: *mut F = mem::transmute(p_arg); - assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); + assert!(!boxed_hook.is_null(), + "Internal error - null function pointer"); let action = Action::from(action_code); let db_name = { @@ -270,7 +274,8 @@ mod test { called = true; false }); - db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;").unwrap(); + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") + .unwrap(); assert!(called); } @@ -279,10 +284,9 @@ mod test { let db = Connection::open_in_memory().unwrap(); let mut called = false; - db.rollback_hook(|| { - called = true; - }); - db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;").unwrap(); + db.rollback_hook(|| { called = true; }); + db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;") + .unwrap(); assert!(called); } @@ -299,8 +303,7 @@ mod test { called = true; }); db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap(); - db.execute_batch("INSERT INTO foo VALUES ('lisa')") - .unwrap(); + db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap(); assert!(called); } } From aa64e2fb33755c696337d443ac4e8af93551ad05 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 5 Dec 2017 19:40:09 -0500 Subject: [PATCH 08/28] Remove potentially conflicting impl of ToSqlOutput. Replace with manual implementations of all the types we provided impls for before. --- src/types/to_sql.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs index 53e4f38..ba8f5da 100644 --- a/src/types/to_sql.rs +++ b/src/types/to_sql.rs @@ -15,6 +15,8 @@ pub enum ToSqlOutput<'a> { ZeroBlob(i32), } +// Generically allow any type that can be converted into a ValueRef +// to be converted into a ToSqlOutput as well. impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a> where &'a T: Into> { @@ -23,11 +25,31 @@ impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a> } } -impl<'a, T: Into> From for ToSqlOutput<'a> { - fn from(t: T) -> Self { - ToSqlOutput::Owned(t.into()) - } -} +// We cannot also generically allow any type that can be converted +// into a Value to be converted into a ToSqlOutput because of +// coherence rules (https://github.com/rust-lang/rust/pull/46192), +// so we'll manually implement it for all the types we know can +// be converted into Values. +macro_rules! from_value( + ($t:ty) => ( + impl<'a> From<$t> for ToSqlOutput<'a> { + fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())} + } + ) +); +from_value!(String); +from_value!(Null); +from_value!(bool); +from_value!(i8); +from_value!(i16); +from_value!(i32); +from_value!(i64); +from_value!(isize); +from_value!(u8); +from_value!(u16); +from_value!(u32); +from_value!(f64); +from_value!(Vec); impl<'a> ToSql for ToSqlOutput<'a> { fn to_sql(&self) -> Result { From 5d8a840b5dc9114030bcfe44c29eb0b0e64697cb Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 24 Dec 2017 09:02:40 +0000 Subject: [PATCH 09/28] Fix date/time format for SQLite, use RFC 3339 We implement `ToSql` and `FromSql` for `time::Timespec` values. Our documentation indicates that we store the value in the same format used by SQLite's built-in date/time functions, but this was not correct. We were using the format: %Y-%m-%d %H:%M:%S:%f %Z This format cannot be interpreted at all by SQLite's built-in date/time functions. There are three reasons for this: - SQLite supports only two timezone formats: `[+-]HH:MM` and the literal character `Z` (indicating UTC) - SQLite does not support a space before the timezone indicator - SQLite supports a period (`.`) between the seconds field and the fractional seconds field, but not a colon (`:`) SQLite does support the RFC 3339 date/time format, which is standard in many other places. As we're always storing a UTC value, we'll simply use a trailing `Z` to indicate the timezone, as allowed by RFC 3339. The new format is: %Y-%m-%dT%H:%M:%S.%fZ To avoid breaking applications using databases with values in the old format, we'll continue to support it as a fallback for `FromSql`. [1] https://www.sqlite.org/lang_datefunc.html [2] https://tools.ietf.org/html/rfc3339 --- src/types/mod.rs | 13 +++++++------ src/types/time.rs | 13 ++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index c998d89..74757cb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -10,12 +10,13 @@ //! * Strings (`String` and `&str`) //! * Blobs (`Vec` and `&[u8]`) //! -//! Additionally, because it is such a common data type, implementations are provided for -//! `time::Timespec` that use a string for storage (using the same format string, -//! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin -//! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage -//! truncates timespecs to the nearest second. If you want different storage for timespecs, you can -//! use a newtype. For example, to store timespecs as `f64`s: +//! Additionally, because it is such a common data type, implementations are +//! provided for `time::Timespec` that use the RFC 3339 date/time format, +//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values +//! can be parsed by SQLite's builtin +//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you +//! want different storage for timespecs, you can use a newtype. For example, to +//! store timespecs as `f64`s: //! //! ```rust //! extern crate rusqlite; diff --git a/src/types/time.rs b/src/types/time.rs index f0dd46c..458435c 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -3,7 +3,8 @@ extern crate time; use Result; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; -const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; +const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; +const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; impl ToSql for time::Timespec { fn to_sql(&self) -> Result { @@ -19,10 +20,12 @@ impl FromSql for time::Timespec { fn column_result(value: ValueRef) -> FromSqlResult { value .as_str() - .and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) { - Ok(tm) => Ok(tm.to_timespec()), - Err(err) => Err(FromSqlError::Other(Box::new(err))), - }) + .and_then(|s| { + time::strptime(s, SQLITE_DATETIME_FMT) + .or_else(|err| { + time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) + .or(Err(FromSqlError::Other(Box::new(err))))})}) + .map(|tm| tm.to_timespec()) } } From 2a03c1ad4d967ae35f0d9edbc4965e082b8755f1 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Thu, 11 Jan 2018 16:52:32 -0800 Subject: [PATCH 10/28] Add DropBehavior::Panic to enforce intentional commit or rollback. --- src/transaction.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/transaction.rs b/src/transaction.rs index 41414f7..ac03b24 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -26,6 +26,9 @@ pub enum DropBehavior { /// Do not commit or roll back changes - this will leave the transaction or savepoint /// open, so should be used with care. Ignore, + + /// Panic. Used to enforce intentional behavior during development. + Panic, } /// Old name for `Transaction`. `SqliteTransaction` is deprecated. @@ -192,6 +195,7 @@ impl<'conn> Transaction<'conn> { DropBehavior::Commit => self.commit_(), DropBehavior::Rollback => self.rollback_(), DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), } } } @@ -303,6 +307,7 @@ impl<'conn> Savepoint<'conn> { DropBehavior::Commit => self.commit_(), DropBehavior::Rollback => self.rollback(), DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), } } } From 96e5cf2239fd6d13c875bcfbe081a684dab4e746 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Feb 2018 10:11:39 +0100 Subject: [PATCH 11/28] Fix statement cache SQLite ignores the SQL tail (whitespaces, comments). We can easily ignore whitespaces by trimming the SQL. --- src/cache.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index bfafbac..d5bb5b4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -46,6 +46,7 @@ impl Connection { self.cache.set_capacity(capacity) } + /// Remove/finalize all prepared statements currently in the cache. pub fn flush_prepared_statement_cache(&self) { self.cache.flush() } @@ -124,7 +125,7 @@ impl StatementCache { sql: &str) -> Result> { let mut cache = self.0.borrow_mut(); - let stmt = match cache.remove(sql) { + let stmt = match cache.remove(sql.trim()) { Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)), None => conn.prepare(sql), }; @@ -135,7 +136,7 @@ impl StatementCache { fn cache_stmt(&self, stmt: RawStatement) { let mut cache = self.0.borrow_mut(); stmt.clear_bindings(); - let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string(); + let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).trim().to_string(); cache.insert(sql, stmt); } @@ -285,4 +286,27 @@ mod test { conn.close().expect("connection not closed"); } + + #[test] + fn test_cache_key() { + let db = Connection::open_in_memory().unwrap(); + let cache = &db.cache; + assert_eq!(0, cache.len()); + + //let sql = " PRAGMA schema_version; -- comment"; + let sql = "PRAGMA schema_version; "; + { + let mut stmt = db.prepare_cached(sql).unwrap(); + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); + } + assert_eq!(1, cache.len()); + + { + let mut stmt = db.prepare_cached(sql).unwrap(); + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); + } + assert_eq!(1, cache.len()); + } } From 5d91fb088f13b8728d0012fb57d28040a4568a17 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 20 Sep 2017 21:28:19 +0200 Subject: [PATCH 12/28] Unlock notification --- Cargo.toml | 1 + libsqlite3-sys/Cargo.toml | 2 + libsqlite3-sys/build.rs | 11 ++++-- src/lib.rs | 2 + src/unlock_notify.rs | 80 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/unlock_notify.rs 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 From 455e7d40605b6f5ef879dee1f8d226965c636b29 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Sep 2017 19:27:07 +0200 Subject: [PATCH 13/28] WIP: Unlock Notification To do: unlock_notify_cb --- src/lib.rs | 43 ++++++++++++++++++------ src/raw_statement.rs | 31 +++++++++++++++--- src/statement.rs | 4 +-- src/unlock_notify.rs | 78 ++++++++++++++++++++++++-------------------- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 238ed09..8a2df1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,8 +125,7 @@ pub mod limits; mod hooks; #[cfg(feature = "hooks")] pub use hooks::*; -#[cfg(feature = "unlock_notify")] -pub mod unlock_notify; +mod unlock_notify; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; @@ -864,16 +863,40 @@ impl InnerConnection { } let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let c_sql = try!(str_to_cstring(sql)); + let len_with_nul = (sql.len() + 1) as c_int; let r = unsafe { - let len_with_nul = (sql.len() + 1) as c_int; - ffi::sqlite3_prepare_v2(self.db(), - c_sql.as_ptr(), - len_with_nul, - &mut c_stmt, - ptr::null_mut()) + if cfg!(feature = "unlock_notify") { + let mut rc; + loop { + rc = ffi::sqlite3_prepare_v2( + self.db(), + c_sql.as_ptr(), + len_with_nul, + &mut c_stmt, + ptr::null_mut(), + ); + if rc != ffi::SQLITE_LOCKED { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.db); + if rc != ffi::SQLITE_OK { + break; + } + } + rc + } else { + ffi::sqlite3_prepare_v2( + self.db(), + c_sql.as_ptr(), + len_with_nul, + &mut c_stmt, + ptr::null_mut(), + ) + } }; - self.decode_result(r) - .map(|_| Statement::new(conn, RawStatement::new(c_stmt))) + self.decode_result(r).map(|_| { + Statement::new(conn, RawStatement::new(c_stmt, self.db())) + }) } fn changes(&mut self) -> c_int { diff --git a/src/raw_statement.rs b/src/raw_statement.rs index d25c09f..b92e122 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -2,14 +2,15 @@ use std::ffi::CStr; use std::ptr; use std::os::raw::c_int; use super::ffi; +use super::unlock_notify; // Private newtype for raw sqlite3_stmts that finalize themselves when dropped. #[derive(Debug)] -pub struct RawStatement(*mut ffi::sqlite3_stmt); +pub struct RawStatement(*mut ffi::sqlite3_stmt, *mut ffi::sqlite3); impl RawStatement { - pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement { - RawStatement(stmt) + pub fn new(stmt: *mut ffi::sqlite3_stmt, db: *mut ffi::sqlite3) -> RawStatement { + RawStatement(stmt, db) } pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { @@ -29,7 +30,29 @@ impl RawStatement { } pub fn step(&self) -> c_int { - unsafe { ffi::sqlite3_step(self.0) } + if cfg!(feature = "unlock_notify") { + let mut rc; + loop { + rc = unsafe { ffi::sqlite3_step(self.0) }; + if rc == ffi::SQLITE_LOCKED { + if unsafe { ffi::sqlite3_extended_errcode(self.1) } != + ffi::SQLITE_LOCKED_SHAREDCACHE + { + break; + } + } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.1); + if rc != ffi::SQLITE_OK { + break; + } + self.reset(); + } + rc + } else { + unsafe { ffi::sqlite3_step(self.0) } + } } pub fn reset(&self) -> c_int { diff --git a/src/statement.rs b/src/statement.rs index 2674c32..2c5227b 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -462,7 +462,7 @@ impl<'conn> Statement<'conn> { } fn finalize_(&mut self) -> Result<()> { - let mut stmt = RawStatement::new(ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); self.conn.decode_result(stmt.finalize()) } @@ -470,7 +470,7 @@ impl<'conn> Statement<'conn> { impl<'conn> Into for Statement<'conn> { fn into(mut self) -> RawStatement { - let mut stmt = RawStatement::new(ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); stmt } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index ac0a2d3..4ef4dd5 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -1,16 +1,20 @@ //! [Unlock Notification](http://sqlite.org/unlock_notify.html) -use std::sync::{Mutex, Condvar}; -use std::os::raw::{c_char, c_int, c_void}; +#[cfg(feature = "unlock_notify")] +use std::sync::{Condvar, Mutex}; +use std::os::raw::c_int; +#[cfg(feature = "unlock_notify")] +use std::os::raw::c_void; use ffi; -use InnerConnection; +#[cfg(feature = "unlock_notify")] struct UnlockNotification { - cond: Condvar, // Condition variable to wait on + cond: Condvar, // Condition variable to wait on mutex: Mutex, // Mutex to protect structure } +#[cfg(feature = "unlock_notify")] impl UnlockNotification { fn new() -> UnlockNotification { UnlockNotification { @@ -34,8 +38,9 @@ impl UnlockNotification { } /// This function is an unlock-notify callback +#[cfg(feature = "unlock_notify")] unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { - /*int i; + /*int i; for(i=0; imutex); @@ -45,36 +50,37 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { }*/ } -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 +/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` +/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the +/// associated database connection. +/// +/// This function calls `sqlite3_unlock_notify()` to register for an +/// unlock-notify callback, then blocks until that callback is delivered +/// and returns `SQLITE_OK`. The caller should then retry the failed operation. +/// +/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock +/// the system, then this function returns `SQLITE_LOCKED` immediately. In +/// this case the caller should not retry the operation and should roll +/// back the current transaction (if any). +#[cfg(feature = "unlock_notify")] +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { + let mut un = UnlockNotification::new(); + /* Register for an unlock-notify callback. */ + let rc = unsafe { + ffi::sqlite3_unlock_notify( + 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 +} - 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 +#[cfg(not(feature = "unlock_notify"))] +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { + unreachable!() +} From 154a70d41b5aa324941e404cc6c712cccd6fd148 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Sep 2017 20:19:23 +0200 Subject: [PATCH 14/28] WIP: Unlock Notification To be tested --- src/unlock_notify.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 4ef4dd5..9676dd3 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -40,14 +40,12 @@ impl UnlockNotification { /// This function is an unlock-notify callback #[cfg(feature = "unlock_notify")] 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); - }*/ + use std::slice::from_raw_parts; + let args = from_raw_parts(ap_arg, n_arg as usize); + for arg in args { + let un: &mut UnlockNotification = &mut *(*arg as *mut UnlockNotification); + un.fired(); + } } /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` From 5fd76aa54b0a4cf19b0964cb2aae6e1f4c0c460d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 22 Sep 2017 23:27:05 +0200 Subject: [PATCH 15/28] Unlock notification Test added --- src/raw_statement.rs | 4 ++-- src/unlock_notify.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/raw_statement.rs b/src/raw_statement.rs index b92e122..08d6873 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -35,8 +35,8 @@ impl RawStatement { loop { rc = unsafe { ffi::sqlite3_step(self.0) }; if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } != - ffi::SQLITE_LOCKED_SHAREDCACHE + if unsafe { ffi::sqlite3_extended_errcode(self.1) } + != ffi::SQLITE_LOCKED_SHAREDCACHE { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 9676dd3..724358f 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -82,3 +82,36 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { unreachable!() } + +#[cfg(feature = "unlock_notify")] +#[cfg(test)] +mod test { + use std::sync::mpsc::sync_channel; + use std::thread; + use std::time; + use {Connection, Result, Transaction, TransactionBehavior, SQLITE_OPEN_READ_WRITE, + SQLITE_OPEN_URI}; + + #[test] + fn test_unlock_notify() { + let url = "file::memory:?cache=shared"; + let flags = SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_URI; + let db1 = Connection::open_with_flags(url, flags).unwrap(); + db1.execute_batch("CREATE TABLE foo (x)").unwrap(); + let (sender, receiver) = sync_channel(0); + let sender2 = sender.clone(); + let child = thread::spawn(move || { + let mut db2 = Connection::open_with_flags(url, flags).unwrap(); + let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap(); + tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap(); + sender2.send(1).unwrap(); + let ten_millis = time::Duration::from_millis(10); + thread::sleep(ten_millis); + tx2.commit().unwrap(); + }); + assert_eq!(receiver.recv().unwrap(), 1); + let the_answer: Result = db1.query_row("SELECT x FROM foo", &[], |r| r.get(0)); + assert_eq!(42i64, the_answer.unwrap()); + child.join().unwrap(); + } +} From c612a44207ba6eeb6dd6051b0d6640b0e6649838 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Mar 2018 10:15:10 +0100 Subject: [PATCH 16/28] Fix errors relative to OpenFlags Also handle extended error codes. --- .travis.yml | 1 + src/lib.rs | 2 +- src/unlock_notify.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index de9a910..d3aa772 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ script: - cargo test --features serde_json - cargo test --features bundled - cargo test --features sqlcipher + - cargo test --features "unlock_notify bundled" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" diff --git a/src/lib.rs b/src/lib.rs index 8a2df1c..d9389ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,7 +875,7 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - if rc != ffi::SQLITE_LOCKED { + if (rc & 0xFF) != ffi::SQLITE_LOCKED { break; } rc = unlock_notify::wait_for_unlock_notify(self.db); diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 724358f..5aabfd2 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -71,7 +71,9 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { &mut un as *mut UnlockNotification as *mut c_void, ) }; - debug_assert!(rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_OK); + debug_assert!(rc == ffi::SQLITE_LOCKED || + rc == ffi::SQLITE_LOCKED_SHAREDCACHE || + rc == ffi::SQLITE_OK); if rc == ffi::SQLITE_OK { un.wait(); } @@ -89,27 +91,25 @@ mod test { use std::sync::mpsc::sync_channel; use std::thread; use std::time; - use {Connection, Result, Transaction, TransactionBehavior, SQLITE_OPEN_READ_WRITE, - SQLITE_OPEN_URI}; + use {Connection, OpenFlags, Result, Transaction, TransactionBehavior}; #[test] fn test_unlock_notify() { let url = "file::memory:?cache=shared"; - let flags = SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_URI; + let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI; let db1 = Connection::open_with_flags(url, flags).unwrap(); db1.execute_batch("CREATE TABLE foo (x)").unwrap(); - let (sender, receiver) = sync_channel(0); - let sender2 = sender.clone(); + let (rx, tx) = sync_channel(0); let child = thread::spawn(move || { let mut db2 = Connection::open_with_flags(url, flags).unwrap(); let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap(); tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap(); - sender2.send(1).unwrap(); + rx.send(1).unwrap(); let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); tx2.commit().unwrap(); }); - assert_eq!(receiver.recv().unwrap(), 1); + assert_eq!(tx.recv().unwrap(), 1); let the_answer: Result = db1.query_row("SELECT x FROM foo", &[], |r| r.get(0)); assert_eq!(42i64, the_answer.unwrap()); child.join().unwrap(); From 83c8db2d21d9be3aeb108e163de17087468e4747 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Mar 2018 10:58:42 +0100 Subject: [PATCH 17/28] Fix Connection::open documentation (#332) --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0ccd22f..98e62da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ impl Connection { /// Open a new connection to a SQLite database. /// /// `Connection::open(path)` is equivalent to `Connection::open_with_flags(path, - /// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`. + /// OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE)`. /// /// # Failure /// From 50d379b564da9586ea0f93e3d80658acf3464159 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 20:07:46 +0200 Subject: [PATCH 18/28] Make Statement::column_index case insensitive Fix #330 --- src/statement.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/statement.rs b/src/statement.rs index 2674c32..09c3bc3 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -45,7 +45,7 @@ impl<'conn> Statement<'conn> { let bytes = name.as_bytes(); let n = self.column_count(); for i in 0..n { - if bytes == self.stmt.column_name(i).to_bytes() { + if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { return Ok(i); } } @@ -785,4 +785,30 @@ mod test { let y: Result = stmt.query_row(&[&1i32], |r| r.get(0)); assert_eq!(3i64, y.unwrap()); } + + #[test] + fn test_query_by_column_name() { + let db = Connection::open_in_memory().unwrap(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql).unwrap(); + let mut stmt = db.prepare("SELECT y FROM foo").unwrap(); + let y: Result = stmt.query_row(&[], |r| r.get("y")); + assert_eq!(3i64, y.unwrap()); + } + + #[test] + fn test_query_by_column_name_ignore_case() { + let db = Connection::open_in_memory().unwrap(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql).unwrap(); + let mut stmt = db.prepare("SELECT y as Y FROM foo").unwrap(); + let y: Result = stmt.query_row(&[], |r| r.get("y")); + assert_eq!(3i64, y.unwrap()); + } } From 7f193c03f1131a32b76d304cc8286d33181ea09c Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 20:40:41 +0200 Subject: [PATCH 19/28] Upgrade stable version used by appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3b87004..155f6e7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: matrix: - - TARGET: 1.21.0-x86_64-pc-windows-gnu + - TARGET: 1.24.1-x86_64-pc-windows-gnu MSYS2_BITS: 64 - - TARGET: 1.21.0-x86_64-pc-windows-msvc + - TARGET: 1.24.1-x86_64-pc-windows-msvc VCPKG_DEFAULT_TRIPLET: x64-windows VCPKGRS_DYNAMIC: 1 - TARGET: nightly-x86_64-pc-windows-msvc From cccdf9735f797f0c71901e8591a26d033436fb20 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 21:49:09 +0200 Subject: [PATCH 20/28] Factorize check on code returned by prepare/step. --- src/lib.rs | 5 +---- src/raw_statement.rs | 11 +---------- src/unlock_notify.rs | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9389ab..97f28f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,10 +875,7 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - if (rc & 0xFF) != ffi::SQLITE_LOCKED { - break; - } - rc = unlock_notify::wait_for_unlock_notify(self.db); + rc = unlock_notify::wait_for_unlock_notify(self.db, rc); if rc != ffi::SQLITE_OK { break; } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 08d6873..a09725d 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -34,16 +34,7 @@ impl RawStatement { let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } - != ffi::SQLITE_LOCKED_SHAREDCACHE - { - break; - } - } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { - break; - } - rc = unlock_notify::wait_for_unlock_notify(self.1); + rc = unlock_notify::wait_for_unlock_notify(self.1, rc); if rc != ffi::SQLITE_OK { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 5aabfd2..2743dd3 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -61,7 +61,14 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { /// this case the caller should not retry the operation and should roll /// back the current transaction (if any). #[cfg(feature = "unlock_notify")] -pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { + if rc == ffi::SQLITE_LOCKED { + if unsafe { ffi::sqlite3_extended_errcode(self.1) } != ffi::SQLITE_LOCKED_SHAREDCACHE { + return rc; + } + } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { + return rc; + } let mut un = UnlockNotification::new(); /* Register for an unlock-notify callback. */ let rc = unsafe { @@ -71,9 +78,9 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { &mut un as *mut UnlockNotification as *mut c_void, ) }; - debug_assert!(rc == ffi::SQLITE_LOCKED || - rc == ffi::SQLITE_LOCKED_SHAREDCACHE || - rc == ffi::SQLITE_OK); + debug_assert!( + rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK + ); if rc == ffi::SQLITE_OK { un.wait(); } @@ -81,7 +88,7 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { } #[cfg(not(feature = "unlock_notify"))] -pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3, _code: c_int) -> c_int { unreachable!() } From a0151f907320d99a1f74cfb5da4384c0ca48788d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 31 Mar 2018 10:22:19 +0200 Subject: [PATCH 21/28] Introduce is_locked --- src/lib.rs | 5 ++++- src/raw_statement.rs | 5 ++++- src/unlock_notify.rs | 24 +++++++++++++++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97f28f5..0a30078 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -875,7 +875,10 @@ impl InnerConnection { &mut c_stmt, ptr::null_mut(), ); - rc = unlock_notify::wait_for_unlock_notify(self.db, rc); + if !unlock_notify::is_locked(self.db, rc) { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.db); if rc != ffi::SQLITE_OK { break; } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index a09725d..9b756da 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -34,7 +34,10 @@ impl RawStatement { let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - rc = unlock_notify::wait_for_unlock_notify(self.1, rc); + if !unlock_notify::is_locked(self.1, rc) { + break; + } + rc = unlock_notify::wait_for_unlock_notify(self.1); if rc != ffi::SQLITE_OK { break; } diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 2743dd3..985958b 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -48,6 +48,14 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { } } +#[cfg(feature = "unlock_notify")] +pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { + return rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { + ffi::sqlite3_extended_errcode(db) + } + == ffi::SQLITE_LOCKED_SHAREDCACHE; +} + /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` /// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the /// associated database connection. @@ -61,14 +69,7 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { /// this case the caller should not retry the operation and should roll /// back the current transaction (if any). #[cfg(feature = "unlock_notify")] -pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { - if rc == ffi::SQLITE_LOCKED { - if unsafe { ffi::sqlite3_extended_errcode(self.1) } != ffi::SQLITE_LOCKED_SHAREDCACHE { - return rc; - } - } else if rc != ffi::SQLITE_LOCKED_SHAREDCACHE { - return rc; - } +pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { let mut un = UnlockNotification::new(); /* Register for an unlock-notify callback. */ let rc = unsafe { @@ -88,7 +89,12 @@ pub fn wait_for_unlock_notify(db: *mut ffi::sqlite3, rc: c_int) -> c_int { } #[cfg(not(feature = "unlock_notify"))] -pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3, _code: c_int) -> c_int { +pub fn is_locked(_db: *mut ffi::sqlite3, _rc: c_int) -> bool { + unreachable!() +} + +#[cfg(not(feature = "unlock_notify"))] +pub fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int { unreachable!() } From 83775ee62d6ca8d281c9dc25c96fb669127bd8cd Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Apr 2018 20:18:51 +0200 Subject: [PATCH 22/28] Remove second field from RawStatement Use sqlite3_db_handle instead --- src/lib.rs | 2 +- src/raw_statement.rs | 11 ++++++----- src/statement.rs | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a30078..3f71362 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -895,7 +895,7 @@ impl InnerConnection { } }; self.decode_result(r).map(|_| { - Statement::new(conn, RawStatement::new(c_stmt, self.db())) + Statement::new(conn, RawStatement::new(c_stmt)) }) } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 9b756da..3c33ea3 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -6,11 +6,11 @@ use super::unlock_notify; // Private newtype for raw sqlite3_stmts that finalize themselves when dropped. #[derive(Debug)] -pub struct RawStatement(*mut ffi::sqlite3_stmt, *mut ffi::sqlite3); +pub struct RawStatement(*mut ffi::sqlite3_stmt); impl RawStatement { - pub fn new(stmt: *mut ffi::sqlite3_stmt, db: *mut ffi::sqlite3) -> RawStatement { - RawStatement(stmt, db) + pub fn new(stmt: *mut ffi::sqlite3_stmt) -> RawStatement { + RawStatement(stmt) } pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { @@ -31,13 +31,14 @@ impl RawStatement { pub fn step(&self) -> c_int { if cfg!(feature = "unlock_notify") { + let db = unsafe { ffi::sqlite3_db_handle(self.0) }; let mut rc; loop { rc = unsafe { ffi::sqlite3_step(self.0) }; - if !unlock_notify::is_locked(self.1, rc) { + if !unlock_notify::is_locked(db, rc) { break; } - rc = unlock_notify::wait_for_unlock_notify(self.1); + rc = unlock_notify::wait_for_unlock_notify(db); if rc != ffi::SQLITE_OK { break; } diff --git a/src/statement.rs b/src/statement.rs index 2c5227b..2674c32 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -462,7 +462,7 @@ impl<'conn> Statement<'conn> { } fn finalize_(&mut self) -> Result<()> { - let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); self.conn.decode_result(stmt.finalize()) } @@ -470,7 +470,7 @@ impl<'conn> Statement<'conn> { impl<'conn> Into for Statement<'conn> { fn into(mut self) -> RawStatement { - let mut stmt = RawStatement::new(ptr::null_mut(), ptr::null_mut()); + let mut stmt = RawStatement::new(ptr::null_mut()); mem::swap(&mut stmt, &mut self.stmt); stmt } From 7c4105afd9569fc86a45186573dc304519748eac Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 6 Apr 2018 21:37:03 +0200 Subject: [PATCH 23/28] Upgrade dependencies bindgen from 0.32 to 0.35 lazy_static from 0.2 to 1.0 --- Cargo.toml | 2 +- libsqlite3-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..1c55343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" -lazy_static = "0.2" +lazy_static = "1.0" regex = "0.2" [dependencies.libsqlite3-sys] diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..1127b7c 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -23,7 +23,7 @@ min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] [build-dependencies] -bindgen = { version = "0.32", optional = true } +bindgen = { version = "0.35", optional = true } pkg-config = { version = "0.3", optional = true } cc = { version = "1.0", optional = true } From cd26d53a23d895811c8b70a3a7ebcae75ccc51bf Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 27 Apr 2018 19:06:34 +0200 Subject: [PATCH 24/28] Upgrade dependencies bindgen 0.36 lazy_static 1.0 --- Cargo.toml | 2 +- libsqlite3-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..1c55343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" -lazy_static = "0.2" +lazy_static = "1.0" regex = "0.2" [dependencies.libsqlite3-sys] diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..64e3f1e 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -23,7 +23,7 @@ min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] [build-dependencies] -bindgen = { version = "0.32", optional = true } +bindgen = { version = "0.36", optional = true } pkg-config = { version = "0.3", optional = true } cc = { version = "1.0", optional = true } From 9c36d29f5e21a5d2091bbc1785e729afd5b7f194 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 2 May 2018 18:21:35 +0200 Subject: [PATCH 25/28] Upgrade regexp dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1c55343..fdf88bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" lazy_static = "1.0" -regex = "0.2" +regex = "1.0" [dependencies.libsqlite3-sys] path = "libsqlite3-sys" From b0e22fc372d1cd6cdc443067547f92b1df16ab1b Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 4 May 2018 18:07:11 +0200 Subject: [PATCH 26/28] Wait in a loop --- src/unlock_notify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 985958b..491815c 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -30,7 +30,7 @@ impl UnlockNotification { fn wait(&mut self) -> bool { let mut fired = self.mutex.lock().unwrap(); - if !*fired { + while !*fired { fired = self.cond.wait(fired).unwrap(); } *fired From 403e840c4a030fa6e4d04a1c4b2414fa48a38e0d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 4 May 2018 19:05:48 +0200 Subject: [PATCH 27/28] Fix clippy warnings --- src/unlock_notify.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs index 491815c..b11c432 100644 --- a/src/unlock_notify.rs +++ b/src/unlock_notify.rs @@ -28,12 +28,11 @@ impl UnlockNotification { self.cond.notify_one(); } - fn wait(&mut self) -> bool { + fn wait(&mut self) { let mut fired = self.mutex.lock().unwrap(); while !*fired { fired = self.cond.wait(fired).unwrap(); } - *fired } } @@ -50,10 +49,10 @@ unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { #[cfg(feature = "unlock_notify")] pub fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { - return rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { + rc == ffi::SQLITE_LOCKED_SHAREDCACHE || (rc & 0xFF) == ffi::SQLITE_LOCKED && unsafe { ffi::sqlite3_extended_errcode(db) } - == ffi::SQLITE_LOCKED_SHAREDCACHE; + == ffi::SQLITE_LOCKED_SHAREDCACHE } /// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` From 994d40da26ccf97a22981567bbae96886c426cdb Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 4 May 2018 19:55:55 +0200 Subject: [PATCH 28/28] Fix clippy warnings --- appveyor.yml | 4 ++-- libsqlite3-sys/src/error.rs | 2 +- src/backup.rs | 2 +- src/blob.rs | 8 ++++---- src/cache.rs | 2 +- src/functions.rs | 19 +++++++++---------- src/hooks.rs | 7 +++---- src/lib.rs | 2 +- src/load_extension_guard.rs | 2 +- src/row.rs | 6 +++--- src/statement.rs | 4 ++-- src/transaction.rs | 8 ++++---- src/types/time.rs | 2 +- 13 files changed, 33 insertions(+), 35 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 155f6e7..3a7c0a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: matrix: - - TARGET: 1.24.1-x86_64-pc-windows-gnu + - TARGET: 1.25.0-x86_64-pc-windows-gnu MSYS2_BITS: 64 - - TARGET: 1.24.1-x86_64-pc-windows-msvc + - TARGET: 1.25.0-x86_64-pc-windows-msvc VCPKG_DEFAULT_TRIPLET: x64-windows VCPKGRS_DYNAMIC: 1 - TARGET: nightly-x86_64-pc-windows-msvc diff --git a/libsqlite3-sys/src/error.rs b/libsqlite3-sys/src/error.rs index 61c80f9..236ad90 100644 --- a/libsqlite3-sys/src/error.rs +++ b/libsqlite3-sys/src/error.rs @@ -91,7 +91,7 @@ impl Error { }; Error { - code: code, + code, extended_code: result_code, } } diff --git a/src/backup.rs b/src/backup.rs index 73ed119..96bbc45 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -208,7 +208,7 @@ impl<'a, 'b> Backup<'a, 'b> { Ok(Backup { phantom_from: PhantomData, phantom_to: PhantomData, - b: b, + b, }) } diff --git a/src/blob.rs b/src/blob.rs index a2b7ca8..1f020f4 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -95,7 +95,7 @@ impl Connection { .map(|_| { Blob { conn: self, - blob: blob, + blob, pos: 0, } }) @@ -205,14 +205,14 @@ impl<'conn> io::Seek for Blob<'conn> { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { let pos = match pos { io::SeekFrom::Start(offset) => offset as i64, - io::SeekFrom::Current(offset) => self.pos as i64 + offset, - io::SeekFrom::End(offset) => self.size() as i64 + offset, + io::SeekFrom::Current(offset) => i64::from(self.pos) + offset, + io::SeekFrom::End(offset) => i64::from(self.size()) + offset, }; if pos < 0 { Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid seek to negative position")) - } else if pos > self.size() as i64 { + } else if pos > i64::from(self.size()) { Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid seek to position past end of blob")) } else { diff --git a/src/cache.rs b/src/cache.rs index d5bb5b4..86db9e3 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -92,7 +92,7 @@ impl<'conn> CachedStatement<'conn> { fn new(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> { CachedStatement { stmt: Some(stmt), - cache: cache, + cache, } } diff --git a/src/functions.rs b/src/functions.rs index 0f792d2..a79b3e6 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -51,7 +51,6 @@ //! ``` use std::error::Error as StdError; use std::ffi::CStr; -use std::mem; use std::ptr; use std::slice; use std::os::raw::{c_int, c_char, c_void}; @@ -183,7 +182,7 @@ impl<'a> ValueRef<'a> { } unsafe extern "C" fn free_boxed_value(p: *mut c_void) { - let _: Box = Box::from_raw(mem::transmute(p)); + let _: Box = Box::from_raw(p as *mut T); } /// Context is a wrapper for the SQLite function evaluation context. @@ -234,7 +233,7 @@ impl<'a> Context<'a> { unsafe { ffi::sqlite3_set_auxdata(self.ctx, arg, - mem::transmute(boxed), + boxed as *mut c_void, Some(free_boxed_value::)) }; } @@ -370,10 +369,10 @@ impl InnerConnection { T: ToSql { let ctx = Context { - ctx: ctx, + ctx, args: slice::from_raw_parts(argv, argc as usize), }; - let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx)); + let boxed_f: *mut F = ffi::sqlite3_user_data(ctx.ctx) as *mut F; assert!(!boxed_f.is_null(), "Internal error - null function pointer"); let t = (*boxed_f)(&ctx); @@ -397,7 +396,7 @@ impl InnerConnection { c_name.as_ptr(), n_arg, flags, - mem::transmute(boxed_f), + boxed_f as *mut c_void, Some(call_boxed_closure::), None, None, @@ -431,7 +430,7 @@ impl InnerConnection { where D: Aggregate, T: ToSql { - let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx)); + let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; assert!(!boxed_aggr.is_null(), "Internal error - null aggregate pointer"); @@ -448,7 +447,7 @@ impl InnerConnection { } let mut ctx = Context { - ctx: ctx, + ctx, args: slice::from_raw_parts(argv, argc as usize), }; @@ -462,7 +461,7 @@ impl InnerConnection { where D: Aggregate, T: ToSql { - let boxed_aggr: *mut D = mem::transmute(ffi::sqlite3_user_data(ctx)); + let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D; assert!(!boxed_aggr.is_null(), "Internal error - null aggregate pointer"); @@ -500,7 +499,7 @@ impl InnerConnection { c_name.as_ptr(), n_arg, flags, - mem::transmute(boxed_aggr), + boxed_aggr as *mut c_void, None, Some(call_boxed_step::), Some(call_boxed_final::), diff --git a/src/hooks.rs b/src/hooks.rs index 4bf0a59..86f4807 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,7 +1,6 @@ //! Commit, Data Change and Rollback Notification Callbacks #![allow(non_camel_case_types)] -use std::mem; use std::ptr; use std::os::raw::{c_int, c_char, c_void}; @@ -154,7 +153,7 @@ impl InnerConnection { unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) -> c_int where F: FnMut() -> bool { - let boxed_hook: *mut F = mem::transmute(p_arg); + let boxed_hook: *mut F = p_arg as *mut F; assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); @@ -178,7 +177,7 @@ impl InnerConnection { unsafe extern "C" fn call_boxed_closure(p_arg: *mut c_void) where F: FnMut() { - let boxed_hook: *mut F = mem::transmute(p_arg); + let boxed_hook: *mut F = p_arg as *mut F; assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); @@ -209,7 +208,7 @@ impl InnerConnection { use std::ffi::CStr; use std::str; - let boxed_hook: *mut F = mem::transmute(p_arg); + let boxed_hook: *mut F = p_arg as *mut F; assert!(!boxed_hook.is_null(), "Internal error - null function pointer"); diff --git a/src/lib.rs b/src/lib.rs index 98e62da..13b659e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -779,7 +779,7 @@ impl InnerConnection { // attempt to turn on extended results code; don't fail if we can't. ffi::sqlite3_extended_result_codes(db, 1); - Ok(InnerConnection { db: db }) + Ok(InnerConnection { db }) } } diff --git a/src/load_extension_guard.rs b/src/load_extension_guard.rs index 7f62a60..d18a1e1 100644 --- a/src/load_extension_guard.rs +++ b/src/load_extension_guard.rs @@ -26,7 +26,7 @@ impl<'conn> LoadExtensionGuard<'conn> { /// guard goes out of scope. Cannot be meaningfully nested. pub fn new(conn: &Connection) -> Result { conn.load_extension_enable() - .map(|_| LoadExtensionGuard { conn: conn }) + .map(|_| LoadExtensionGuard { conn }) } } diff --git a/src/row.rs b/src/row.rs index 6ab0619..2042616 100644 --- a/src/row.rs +++ b/src/row.rs @@ -32,7 +32,7 @@ impl<'stmt> Rows<'stmt> { .and_then(|stmt| match stmt.step() { Ok(true) => { Some(Ok(Row { - stmt: stmt, + stmt, phantom: PhantomData, })) } @@ -90,7 +90,7 @@ impl<'stmt, T, F> MappedRowsCrateImpl<'stmt, T, F> for MappedRows<'stmt, F> where F: FnMut(&Row) -> T { fn new(rows: Rows<'stmt>, f: F) -> MappedRows<'stmt, F> { - MappedRows { rows: rows, map: f } + MappedRows { rows, map: f } } } @@ -124,7 +124,7 @@ impl<'stmt, T, E, F> AndThenRowsCrateImpl<'stmt, T, E, F> for AndThenRows<'stmt, where F: FnMut(&Row) -> result::Result { fn new(rows: Rows<'stmt>, f: F) -> AndThenRows<'stmt, F> { - AndThenRows { rows: rows, map: f } + AndThenRows { rows, map: f } } } diff --git a/src/statement.rs b/src/statement.rs index 09c3bc3..c867ad4 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -506,8 +506,8 @@ pub trait StatementCrateImpl<'conn> { impl<'conn> StatementCrateImpl<'conn> for Statement<'conn> { fn new(conn: &Connection, stmt: RawStatement) -> Statement { Statement { - conn: conn, - stmt: stmt, + conn, + stmt, } } diff --git a/src/transaction.rs b/src/transaction.rs index dd0807c..3b4509a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -109,7 +109,7 @@ impl<'conn> Transaction<'conn> { conn.execute_batch(query) .map(move |_| { Transaction { - conn: conn, + conn, drop_behavior: DropBehavior::Rollback, committed: false, } @@ -227,9 +227,9 @@ impl<'conn> Savepoint<'conn> { conn.execute_batch(&format!("SAVEPOINT {}", name)) .map(|_| { Savepoint { - conn: conn, - name: name, - depth: depth, + conn, + name, + depth, drop_behavior: DropBehavior::Rollback, committed: false, } diff --git a/src/types/time.rs b/src/types/time.rs index 458435c..71a7955 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -24,7 +24,7 @@ impl FromSql for time::Timespec { time::strptime(s, SQLITE_DATETIME_FMT) .or_else(|err| { time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) - .or(Err(FromSqlError::Other(Box::new(err))))})}) + .or_else(|_| Err(FromSqlError::Other(Box::new(err))))})}) .map(|tm| tm.to_timespec()) } }