2020-04-06 13:15:27 +08:00
//! `feature = "hooks"` Commit, Data Change and Rollback Notification Callbacks
2017-04-26 02:58:22 +08:00
#![ allow(non_camel_case_types) ]
2018-08-11 18:48:21 +08:00
use std ::os ::raw ::{ c_char , c_int , c_void } ;
2020-10-25 18:58:47 +08:00
use std ::panic ::{ catch_unwind , RefUnwindSafe } ;
2017-04-26 02:58:22 +08:00
use std ::ptr ;
2018-10-31 03:11:35 +08:00
use crate ::ffi ;
2017-04-26 02:58:22 +08:00
2018-10-31 03:11:35 +08:00
use crate ::{ Connection , InnerConnection } ;
2017-04-26 02:58:22 +08:00
2020-04-06 13:15:27 +08:00
/// `feature = "hooks"` Action Codes
2019-01-13 19:46:19 +08:00
#[ derive(Clone, Copy, Debug, PartialEq) ]
2019-02-02 18:04:46 +08:00
#[ repr(i32) ]
2020-04-07 03:01:39 +08:00
#[ non_exhaustive ]
2017-04-26 02:58:22 +08:00
pub enum Action {
2020-05-17 17:21:10 +08:00
/// Unsupported / unexpected action
2017-04-26 02:58:22 +08:00
UNKNOWN = - 1 ,
2020-05-17 17:21:10 +08:00
/// DELETE command
2019-02-02 18:04:46 +08:00
SQLITE_DELETE = ffi ::SQLITE_DELETE ,
2020-05-17 17:21:10 +08:00
/// INSERT command
2019-02-02 18:04:46 +08:00
SQLITE_INSERT = ffi ::SQLITE_INSERT ,
2020-05-17 17:21:10 +08:00
/// UPDATE command
2019-02-02 18:04:46 +08:00
SQLITE_UPDATE = ffi ::SQLITE_UPDATE ,
2017-04-26 02:58:22 +08:00
}
impl From < i32 > for Action {
fn from ( code : i32 ) -> Action {
match code {
ffi ::SQLITE_DELETE = > Action ::SQLITE_DELETE ,
ffi ::SQLITE_INSERT = > Action ::SQLITE_INSERT ,
ffi ::SQLITE_UPDATE = > Action ::SQLITE_UPDATE ,
_ = > Action ::UNKNOWN ,
}
}
}
impl Connection {
2020-04-06 13:15:27 +08:00
/// `feature = "hooks"` Register a callback function to be invoked whenever
/// a transaction is committed.
2017-05-13 01:14:34 +08:00
///
/// The callback returns `true` to rollback.
2018-08-05 16:58:00 +08:00
pub fn commit_hook < F > ( & self , hook : Option < F > )
where
2018-08-16 02:04:01 +08:00
F : FnMut ( ) -> bool + Send + 'static ,
2017-05-13 01:14:34 +08:00
{
self . db . borrow_mut ( ) . commit_hook ( hook ) ;
}
2020-04-06 13:15:27 +08:00
/// `feature = "hooks"` Register a callback function to be invoked whenever
/// a transaction is committed.
2017-05-13 01:14:34 +08:00
///
/// The callback returns `true` to rollback.
2018-08-05 16:58:00 +08:00
pub fn rollback_hook < F > ( & self , hook : Option < F > )
where
2018-08-16 02:04:01 +08:00
F : FnMut ( ) + Send + 'static ,
2017-05-13 01:14:34 +08:00
{
self . db . borrow_mut ( ) . rollback_hook ( hook ) ;
}
2020-04-06 13:15:27 +08:00
/// `feature = "hooks"` Register a callback function to be invoked whenever
/// a row is updated, inserted or deleted in a rowid table.
2017-05-13 01:14:34 +08:00
///
/// The callback parameters are:
///
2018-08-17 00:29:46 +08:00
/// - 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.
2018-08-05 16:58:00 +08:00
pub fn update_hook < F > ( & self , hook : Option < F > )
where
2018-08-16 02:04:01 +08:00
F : FnMut ( Action , & str , & str , i64 ) + Send + 'static ,
2017-04-26 02:58:22 +08:00
{
self . db . borrow_mut ( ) . update_hook ( hook ) ;
}
2020-10-25 18:58:47 +08:00
/// `feature = "hooks"` Register a query progress callback.
///
/// The parameter `num_ops` is the approximate number of virtual machine instructions that are evaluated between successive invocations of the `handler`.
/// If `num_ops` is less than one then the progress handler is disabled.
///
/// If the progress callback returns `true`, the operation is interrupted.
pub fn progress_handler < F > ( & self , num_ops : c_int , handler : Option < F > )
where
F : FnMut ( ) -> bool + Send + RefUnwindSafe + 'static ,
{
self . db . borrow_mut ( ) . progress_handler ( num_ops , handler ) ;
}
2017-04-26 02:58:22 +08:00
}
impl InnerConnection {
2017-04-27 02:12:48 +08:00
pub fn remove_hooks ( & mut self ) {
2018-08-05 16:58:00 +08:00
self . update_hook ( None ::< fn ( Action , & str , & str , i64 ) > ) ;
self . commit_hook ( None ::< fn ( ) -> bool > ) ;
self . rollback_hook ( None ::< fn ( ) > ) ;
2020-10-25 18:58:47 +08:00
self . progress_handler ( 0 , None ::< fn ( ) -> bool > ) ;
2017-05-13 01:14:34 +08:00
}
2018-08-11 17:14:17 +08:00
fn commit_hook < F > ( & mut self , hook : Option < F > )
2018-08-05 16:58:00 +08:00
where
2018-08-16 02:04:01 +08:00
F : FnMut ( ) -> bool + Send + 'static ,
2017-05-13 01:14:34 +08:00
{
unsafe extern " C " fn call_boxed_closure < F > ( p_arg : * mut c_void ) -> c_int
2018-08-05 16:58:00 +08:00
where
F : FnMut ( ) -> bool ,
2017-05-13 01:14:34 +08:00
{
2018-12-16 16:40:14 +08:00
let r = catch_unwind ( | | {
let boxed_hook : * mut F = p_arg as * mut F ;
( * boxed_hook ) ( )
} ) ;
if let Ok ( true ) = r {
2018-08-05 16:58:00 +08:00
1
} else {
0
}
2017-05-13 01:14:34 +08:00
}
2018-08-17 00:29:46 +08:00
// unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with
// `sqlite3_commit_hook`. so we keep the `xDestroy` function in
// `InnerConnection.free_boxed_hook`.
2018-08-11 17:14:17 +08:00
let free_commit_hook = if hook . is_some ( ) {
2020-04-07 10:38:33 +08:00
Some ( free_boxed_hook ::< F > as unsafe fn ( * mut c_void ) )
2018-08-11 17:14:17 +08:00
} else {
None
} ;
2018-08-05 16:58:00 +08:00
let previous_hook = match hook {
Some ( hook ) = > {
let boxed_hook : * mut F = Box ::into_raw ( Box ::new ( hook ) ) ;
unsafe {
ffi ::sqlite3_commit_hook (
self . db ( ) ,
Some ( call_boxed_closure ::< F > ) ,
boxed_hook as * mut _ ,
)
}
2017-05-13 01:14:34 +08:00
}
2018-08-05 16:58:00 +08:00
_ = > unsafe { ffi ::sqlite3_commit_hook ( self . db ( ) , None , ptr ::null_mut ( ) ) } ,
2017-05-13 01:14:34 +08:00
} ;
2018-08-11 17:14:17 +08:00
if ! previous_hook . is_null ( ) {
if let Some ( free_boxed_hook ) = self . free_commit_hook {
2020-04-07 10:38:33 +08:00
unsafe { free_boxed_hook ( previous_hook ) } ;
2018-08-11 17:14:17 +08:00
}
}
self . free_commit_hook = free_commit_hook ;
2017-05-13 01:14:34 +08:00
}
2018-08-11 17:14:17 +08:00
fn rollback_hook < F > ( & mut self , hook : Option < F > )
2018-08-05 16:58:00 +08:00
where
2018-08-16 02:04:01 +08:00
F : FnMut ( ) + Send + 'static ,
2017-05-13 01:14:34 +08:00
{
unsafe extern " C " fn call_boxed_closure < F > ( p_arg : * mut c_void )
2018-08-05 16:58:00 +08:00
where
F : FnMut ( ) ,
2017-05-13 01:14:34 +08:00
{
2018-12-16 16:40:14 +08:00
let _ = catch_unwind ( | | {
let boxed_hook : * mut F = p_arg as * mut F ;
( * boxed_hook ) ( ) ;
} ) ;
2017-05-13 01:14:34 +08:00
}
2018-08-11 17:14:17 +08:00
let free_rollback_hook = if hook . is_some ( ) {
2020-04-07 10:38:33 +08:00
Some ( free_boxed_hook ::< F > as unsafe fn ( * mut c_void ) )
2018-08-11 17:14:17 +08:00
} else {
None
} ;
2018-08-05 16:58:00 +08:00
let previous_hook = match hook {
Some ( hook ) = > {
let boxed_hook : * mut F = Box ::into_raw ( Box ::new ( hook ) ) ;
unsafe {
ffi ::sqlite3_rollback_hook (
self . db ( ) ,
Some ( call_boxed_closure ::< F > ) ,
boxed_hook as * mut _ ,
)
}
2017-05-13 01:14:34 +08:00
}
2018-08-05 16:58:00 +08:00
_ = > unsafe { ffi ::sqlite3_rollback_hook ( self . db ( ) , None , ptr ::null_mut ( ) ) } ,
2017-05-13 01:14:34 +08:00
} ;
2018-08-11 17:14:17 +08:00
if ! previous_hook . is_null ( ) {
if let Some ( free_boxed_hook ) = self . free_rollback_hook {
2020-04-07 10:38:33 +08:00
unsafe { free_boxed_hook ( previous_hook ) } ;
2018-08-11 17:14:17 +08:00
}
}
self . free_rollback_hook = free_rollback_hook ;
2017-04-27 02:12:48 +08:00
}
2018-08-05 16:58:00 +08:00
fn update_hook < F > ( & mut self , hook : Option < F > )
where
2018-08-16 02:04:01 +08:00
F : FnMut ( Action , & str , & str , i64 ) + Send + 'static ,
2017-04-26 02:58:22 +08:00
{
2018-08-05 16:58:00 +08:00
unsafe extern " C " fn call_boxed_closure < F > (
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 ) ,
2017-04-26 02:58:22 +08:00
{
use std ::ffi ::CStr ;
use std ::str ;
let action = Action ::from ( action_code ) ;
let db_name = {
let c_slice = CStr ::from_ptr ( db_str ) . to_bytes ( ) ;
2020-04-15 00:07:01 +08:00
str ::from_utf8 ( c_slice )
2017-04-26 02:58:22 +08:00
} ;
let tbl_name = {
let c_slice = CStr ::from_ptr ( tbl_str ) . to_bytes ( ) ;
2020-04-15 00:07:01 +08:00
str ::from_utf8 ( c_slice )
2017-04-26 02:58:22 +08:00
} ;
2018-12-16 16:40:14 +08:00
let _ = catch_unwind ( | | {
let boxed_hook : * mut F = p_arg as * mut F ;
2020-04-15 00:07:01 +08:00
( * boxed_hook ) (
action ,
db_name . expect ( " illegal db name " ) ,
tbl_name . expect ( " illegal table name " ) ,
row_id ,
) ;
2018-12-16 16:40:14 +08:00
} ) ;
2017-04-26 02:58:22 +08:00
}
2018-08-11 17:14:17 +08:00
let free_update_hook = if hook . is_some ( ) {
2020-04-07 10:38:33 +08:00
Some ( free_boxed_hook ::< F > as unsafe fn ( * mut c_void ) )
2018-08-11 17:14:17 +08:00
} else {
None
} ;
2018-08-05 16:58:00 +08:00
let previous_hook = match hook {
Some ( hook ) = > {
let boxed_hook : * mut F = Box ::into_raw ( Box ::new ( hook ) ) ;
unsafe {
ffi ::sqlite3_update_hook (
self . db ( ) ,
Some ( call_boxed_closure ::< F > ) ,
boxed_hook as * mut _ ,
)
}
2017-04-26 02:58:22 +08:00
}
2018-08-05 16:58:00 +08:00
_ = > unsafe { ffi ::sqlite3_update_hook ( self . db ( ) , None , ptr ::null_mut ( ) ) } ,
2017-04-26 02:58:22 +08:00
} ;
2018-08-11 17:14:17 +08:00
if ! previous_hook . is_null ( ) {
if let Some ( free_boxed_hook ) = self . free_update_hook {
2020-04-07 10:38:33 +08:00
unsafe { free_boxed_hook ( previous_hook ) } ;
2018-08-11 17:14:17 +08:00
}
}
self . free_update_hook = free_update_hook ;
2017-04-27 02:12:48 +08:00
}
2020-10-25 18:58:47 +08:00
fn progress_handler < F > ( & mut self , num_ops : c_int , handler : Option < F > )
where
F : FnMut ( ) -> bool + Send + RefUnwindSafe + 'static ,
{
unsafe extern " C " fn call_boxed_closure < F > ( p_arg : * mut c_void ) -> c_int
where
F : FnMut ( ) -> bool ,
{
let r = catch_unwind ( | | {
let boxed_handler : * mut F = p_arg as * mut F ;
( * boxed_handler ) ( )
} ) ;
if let Ok ( true ) = r {
1
} else {
0
}
}
match handler {
Some ( handler ) = > {
let boxed_handler = Box ::new ( handler ) ;
unsafe {
ffi ::sqlite3_progress_handler (
self . db ( ) ,
num_ops ,
Some ( call_boxed_closure ::< F > ) ,
& * boxed_handler as * const F as * mut _ ,
)
}
self . progress_handler = Some ( boxed_handler ) ;
}
_ = > {
unsafe { ffi ::sqlite3_progress_handler ( self . db ( ) , num_ops , None , ptr ::null_mut ( ) ) }
self . progress_handler = None ;
}
} ;
}
2017-04-27 02:12:48 +08:00
}
2020-04-07 10:38:33 +08:00
unsafe fn free_boxed_hook < F > ( p : * mut c_void ) {
drop ( Box ::from_raw ( p as * mut F ) ) ;
2017-04-27 02:12:48 +08:00
}
#[ cfg(test) ]
mod test {
use super ::Action ;
2018-10-31 03:11:35 +08:00
use crate ::Connection ;
2019-08-10 02:01:44 +08:00
use lazy_static ::lazy_static ;
2019-08-10 02:03:46 +08:00
use std ::sync ::atomic ::{ AtomicBool , Ordering } ;
2017-04-27 02:12:48 +08:00
2017-05-13 01:14:34 +08:00
#[ test ]
fn test_commit_hook ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
2018-08-16 02:04:01 +08:00
lazy_static! {
2019-02-02 18:10:08 +08:00
static ref CALLED : AtomicBool = AtomicBool ::new ( false ) ;
2018-08-16 02:04:01 +08:00
}
2018-08-05 16:58:00 +08:00
db . commit_hook ( Some ( | | {
2019-02-02 18:10:08 +08:00
CALLED . store ( true , Ordering ::Relaxed ) ;
2018-08-05 16:58:00 +08:00
false
} ) ) ;
2017-05-13 01:18:42 +08:00
db . execute_batch ( " BEGIN; CREATE TABLE foo (t TEXT); COMMIT; " )
. unwrap ( ) ;
2019-02-02 18:10:08 +08:00
assert! ( CALLED . load ( Ordering ::Relaxed ) ) ;
2017-05-13 01:14:34 +08:00
}
2018-08-11 17:14:17 +08:00
#[ test ]
fn test_fn_commit_hook ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
fn hook ( ) -> bool {
true
}
db . commit_hook ( Some ( hook ) ) ;
db . execute_batch ( " BEGIN; CREATE TABLE foo (t TEXT); COMMIT; " )
. unwrap_err ( ) ;
}
2017-05-13 01:14:34 +08:00
#[ test ]
fn test_rollback_hook ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
2018-08-16 02:04:01 +08:00
lazy_static! {
2019-02-02 18:10:08 +08:00
static ref CALLED : AtomicBool = AtomicBool ::new ( false ) ;
2018-08-16 02:04:01 +08:00
}
2018-08-05 16:58:00 +08:00
db . rollback_hook ( Some ( | | {
2019-02-02 18:10:08 +08:00
CALLED . store ( true , Ordering ::Relaxed ) ;
2018-08-05 16:58:00 +08:00
} ) ) ;
2017-05-13 01:18:42 +08:00
db . execute_batch ( " BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK; " )
. unwrap ( ) ;
2019-02-02 18:10:08 +08:00
assert! ( CALLED . load ( Ordering ::Relaxed ) ) ;
2017-05-13 01:14:34 +08:00
}
2017-04-27 02:12:48 +08:00
#[ test ]
fn test_update_hook ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
2018-08-16 02:04:01 +08:00
lazy_static! {
2019-02-02 18:10:08 +08:00
static ref CALLED : AtomicBool = AtomicBool ::new ( false ) ;
2018-08-16 02:04:01 +08:00
}
2018-08-05 16:58:00 +08:00
db . update_hook ( Some ( | action , db : & str , tbl : & str , row_id | {
assert_eq! ( Action ::SQLITE_INSERT , action ) ;
assert_eq! ( " main " , db ) ;
assert_eq! ( " foo " , tbl ) ;
assert_eq! ( 1 , row_id ) ;
2019-02-02 18:10:08 +08:00
CALLED . store ( true , Ordering ::Relaxed ) ;
2018-08-05 16:58:00 +08:00
} ) ) ;
2017-04-27 02:12:48 +08:00
db . execute_batch ( " CREATE TABLE foo (t TEXT) " ) . unwrap ( ) ;
2017-05-13 01:18:42 +08:00
db . execute_batch ( " INSERT INTO foo VALUES ('lisa') " ) . unwrap ( ) ;
2019-02-02 18:10:08 +08:00
assert! ( CALLED . load ( Ordering ::Relaxed ) ) ;
2017-04-26 02:58:22 +08:00
}
2020-10-25 19:20:15 +08:00
#[ test ]
fn test_progress_handler ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
lazy_static! {
static ref CALLED : AtomicBool = AtomicBool ::new ( false ) ;
}
2020-10-25 19:28:03 +08:00
db . progress_handler (
1 ,
Some ( | | {
CALLED . store ( true , Ordering ::Relaxed ) ;
false
} ) ,
) ;
2020-10-25 19:20:15 +08:00
db . execute_batch ( " BEGIN; CREATE TABLE foo (t TEXT); COMMIT; " )
. unwrap ( ) ;
assert! ( CALLED . load ( Ordering ::Relaxed ) ) ;
}
#[ test ]
fn test_progress_handler_interrupt ( ) {
let db = Connection ::open_in_memory ( ) . unwrap ( ) ;
fn handler ( ) -> bool {
true
}
db . progress_handler ( 1 , Some ( handler ) ) ;
db . execute_batch ( " BEGIN; CREATE TABLE foo (t TEXT); COMMIT; " )
. unwrap_err ( ) ;
}
2017-04-26 02:58:22 +08:00
}