@@ -4,7 +4,10 @@ use crate::auth::{AuthAction, AuthContext, Authorization};
44use crate :: connection:: AuthHook ;
55use crate :: local:: rows:: BatchedRows ;
66use crate :: params:: Params ;
7- use crate :: { connection:: BatchRows , errors} ;
7+ use crate :: {
8+ connection:: { BatchRows , Op , UpdateHook } ,
9+ errors,
10+ } ;
811use std:: time:: Duration ;
912
1013use super :: { Database , Error , Result , Rows , RowsFuture , Statement , Transaction } ;
@@ -15,6 +18,10 @@ use libsql_sys::ffi;
1518use parking_lot:: RwLock ;
1619use std:: { ffi:: c_int, fmt, path:: Path , sync:: Arc } ;
1720
21+ struct Container {
22+ cb : Box < UpdateHook > ,
23+ }
24+
1825/// A connection to a libSQL database.
1926#[ derive( Clone ) ]
2027pub struct Connection {
@@ -400,6 +407,24 @@ impl Connection {
400407 } )
401408 }
402409
410+ /// Installs update hook
411+ pub fn add_update_hook ( & self , cb : Box < UpdateHook > ) {
412+ let c = Box :: new ( Container { cb } ) ;
413+ let ptr: * mut Container = std:: ptr:: from_mut ( Box :: leak ( c) ) ;
414+
415+ let old_data = unsafe {
416+ ffi:: sqlite3_update_hook (
417+ self . raw ,
418+ Some ( update_hook_cb) ,
419+ ptr as * mut :: std:: os:: raw:: c_void ,
420+ )
421+ } ;
422+
423+ if !old_data. is_null ( ) {
424+ let _ = unsafe { Box :: from_raw ( old_data as * mut Container ) } ;
425+ }
426+ }
427+
403428 pub fn enable_load_extension ( & self , onoff : bool ) -> Result < ( ) > {
404429 // SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION configration verb accepts 2 additional parameters: an on/off flag and a pointer to an c_int where new state of the parameter will be written (or NULL if reporting back the setting is not needed)
405430 // See: https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
@@ -464,7 +489,8 @@ impl Connection {
464489
465490 pub fn authorizer ( & self , hook : Option < AuthHook > ) -> Result < ( ) > {
466491 unsafe {
467- let rc = libsql_sys:: ffi:: sqlite3_set_authorizer ( self . handle ( ) , None , std:: ptr:: null_mut ( ) ) ;
492+ let rc =
493+ libsql_sys:: ffi:: sqlite3_set_authorizer ( self . handle ( ) , None , std:: ptr:: null_mut ( ) ) ;
468494 if rc != ffi:: SQLITE_OK {
469495 return Err ( crate :: errors:: Error :: SqliteFailure (
470496 rc as std:: ffi:: c_int ,
@@ -484,7 +510,8 @@ impl Connection {
484510 None => ( None , std:: ptr:: null_mut ( ) ) ,
485511 } ;
486512
487- let rc = unsafe { libsql_sys:: ffi:: sqlite3_set_authorizer ( self . handle ( ) , callback, user_data) } ;
513+ let rc =
514+ unsafe { libsql_sys:: ffi:: sqlite3_set_authorizer ( self . handle ( ) , callback, user_data) } ;
488515 if rc != ffi:: SQLITE_OK {
489516 return Err ( crate :: errors:: Error :: SqliteFailure (
490517 rc as std:: ffi:: c_int ,
@@ -716,7 +743,7 @@ unsafe extern "C" fn authorizer_callback(
716743
717744pub ( crate ) struct WalInsertHandle < ' a > {
718745 conn : & ' a Connection ,
719- in_session : RwLock < bool >
746+ in_session : RwLock < bool > ,
720747}
721748
722749impl WalInsertHandle < ' _ > {
@@ -761,6 +788,28 @@ impl fmt::Debug for Connection {
761788 }
762789}
763790
791+ #[ no_mangle]
792+ extern "C" fn update_hook_cb (
793+ data : * mut :: std:: os:: raw:: c_void ,
794+ op : :: std:: os:: raw:: c_int ,
795+ db_name : * const :: std:: os:: raw:: c_char ,
796+ table_name : * const :: std:: os:: raw:: c_char ,
797+ row_id : i64 ,
798+ ) {
799+ let db = unsafe { std:: ffi:: CStr :: from_ptr ( db_name) . to_string_lossy ( ) } ;
800+ let table = unsafe { std:: ffi:: CStr :: from_ptr ( table_name) . to_string_lossy ( ) } ;
801+
802+ let c = unsafe { & mut * ( data as * mut Container ) } ;
803+ let o = match op {
804+ 9 => Op :: Delete ,
805+ 18 => Op :: Insert ,
806+ 23 => Op :: Update ,
807+ _ => unreachable ! ( "Unknown operation {op}" ) ,
808+ } ;
809+
810+ ( * c. cb ) ( o, & db, & table, row_id) ;
811+ }
812+
764813#[ cfg( test) ]
765814mod tests {
766815 use crate :: {
0 commit comments