@@ -11,8 +11,8 @@ use futures_core::future::BoxFuture;
11
11
use futures_intrusive:: sync:: MutexGuard ;
12
12
use futures_util:: future;
13
13
use libsqlite3_sys:: {
14
- sqlite3, sqlite3_progress_handler , sqlite3_update_hook , SQLITE_DELETE , SQLITE_INSERT ,
15
- SQLITE_UPDATE ,
14
+ sqlite3, sqlite3_commit_hook , sqlite3_progress_handler , sqlite3_rollback_hook ,
15
+ sqlite3_update_hook , SQLITE_DELETE , SQLITE_INSERT , SQLITE_UPDATE ,
16
16
} ;
17
17
18
18
pub ( crate ) use handle:: ConnectionHandle ;
@@ -63,7 +63,7 @@ pub struct LockedSqliteHandle<'a> {
63
63
pub ( crate ) struct Handler ( NonNull < dyn FnMut ( ) -> bool + Send + ' static > ) ;
64
64
unsafe impl Send for Handler { }
65
65
66
- #[ derive( Debug , PartialEq , Eq ) ]
66
+ #[ derive( Debug , PartialEq , Eq , Clone ) ]
67
67
pub enum SqliteOperation {
68
68
Insert ,
69
69
Update ,
@@ -91,6 +91,12 @@ pub struct UpdateHookResult<'a> {
91
91
pub ( crate ) struct UpdateHookHandler ( NonNull < dyn FnMut ( UpdateHookResult ) + Send + ' static > ) ;
92
92
unsafe impl Send for UpdateHookHandler { }
93
93
94
+ pub ( crate ) struct CommitHookHandler ( NonNull < dyn FnMut ( ) -> bool + Send + ' static > ) ;
95
+ unsafe impl Send for CommitHookHandler { }
96
+
97
+ pub ( crate ) struct RollbackHookHandler ( NonNull < dyn FnMut ( ) + Send + ' static > ) ;
98
+ unsafe impl Send for RollbackHookHandler { }
99
+
94
100
pub ( crate ) struct ConnectionState {
95
101
pub ( crate ) handle : ConnectionHandle ,
96
102
@@ -106,6 +112,10 @@ pub(crate) struct ConnectionState {
106
112
progress_handler_callback : Option < Handler > ,
107
113
108
114
update_hook_callback : Option < UpdateHookHandler > ,
115
+
116
+ commit_hook_callback : Option < CommitHookHandler > ,
117
+
118
+ rollback_hook_callback : Option < RollbackHookHandler > ,
109
119
}
110
120
111
121
impl ConnectionState {
@@ -127,6 +137,24 @@ impl ConnectionState {
127
137
}
128
138
}
129
139
}
140
+
141
+ pub ( crate ) fn remove_commit_hook ( & mut self ) {
142
+ if let Some ( mut handler) = self . commit_hook_callback . take ( ) {
143
+ unsafe {
144
+ sqlite3_commit_hook ( self . handle . as_ptr ( ) , None , ptr:: null_mut ( ) ) ;
145
+ let _ = { Box :: from_raw ( handler. 0 . as_mut ( ) ) } ;
146
+ }
147
+ }
148
+ }
149
+
150
+ pub ( crate ) fn remove_rollback_hook ( & mut self ) {
151
+ if let Some ( mut handler) = self . rollback_hook_callback . take ( ) {
152
+ unsafe {
153
+ sqlite3_rollback_hook ( self . handle . as_ptr ( ) , None , ptr:: null_mut ( ) ) ;
154
+ let _ = { Box :: from_raw ( handler. 0 . as_mut ( ) ) } ;
155
+ }
156
+ }
157
+ }
130
158
}
131
159
132
160
pub ( crate ) struct Statements {
@@ -284,6 +312,31 @@ extern "C" fn update_hook<F>(
284
312
}
285
313
}
286
314
315
+ extern "C" fn commit_hook < F > ( callback : * mut c_void ) -> c_int
316
+ where
317
+ F : FnMut ( ) -> bool ,
318
+ {
319
+ unsafe {
320
+ let r = catch_unwind ( || {
321
+ let callback: * mut F = callback. cast :: < F > ( ) ;
322
+ ( * callback) ( )
323
+ } ) ;
324
+ c_int:: from ( !r. unwrap_or_default ( ) )
325
+ }
326
+ }
327
+
328
+ extern "C" fn rollback_hook < F > ( callback : * mut c_void )
329
+ where
330
+ F : FnMut ( ) ,
331
+ {
332
+ unsafe {
333
+ let _ = catch_unwind ( || {
334
+ let callback: * mut F = callback. cast :: < F > ( ) ;
335
+ ( * callback) ( )
336
+ } ) ;
337
+ }
338
+ }
339
+
287
340
impl LockedSqliteHandle < ' _ > {
288
341
/// Returns the underlying sqlite3* connection handle.
289
342
///
@@ -368,6 +421,61 @@ impl LockedSqliteHandle<'_> {
368
421
}
369
422
}
370
423
424
+ /// Sets a commit hook that is invoked whenever a transaction is committed. If the commit hook callback
425
+ /// returns `false`, then the operation is turned into a ROLLBACK.
426
+ ///
427
+ /// Only a single commit hook may be defined at one time per database connection; setting a new commit hook
428
+ /// overrides the old one.
429
+ ///
430
+ /// The commit hook callback must not do anything that will modify the database connection that invoked
431
+ /// the commit hook. Note that sqlite3_prepare_v2() and sqlite3_step() both modify their database connections
432
+ /// in this context.
433
+ ///
434
+ /// See https://www.sqlite.org/c3ref/commit_hook.html
435
+ pub fn set_commit_hook < F > ( & mut self , callback : F )
436
+ where
437
+ F : FnMut ( ) -> bool + Send + ' static ,
438
+ {
439
+ unsafe {
440
+ let callback_boxed = Box :: new ( callback) ;
441
+ // SAFETY: `Box::into_raw()` always returns a non-null pointer.
442
+ let callback = NonNull :: new_unchecked ( Box :: into_raw ( callback_boxed) ) ;
443
+ let handler = callback. as_ptr ( ) as * mut _ ;
444
+ self . guard . remove_commit_hook ( ) ;
445
+ self . guard . commit_hook_callback = Some ( CommitHookHandler ( callback) ) ;
446
+
447
+ sqlite3_commit_hook (
448
+ self . as_raw_handle ( ) . as_mut ( ) ,
449
+ Some ( commit_hook :: < F > ) ,
450
+ handler,
451
+ ) ;
452
+ }
453
+ }
454
+
455
+ /// Sets a rollback hook that is invoked whenever a transaction rollback occurs. The rollback callback is not
456
+ /// invoked if a transaction is automatically rolled back because the database connection is closed.
457
+ ///
458
+ /// See https://www.sqlite.org/c3ref/commit_hook.html
459
+ pub fn set_rollback_hook < F > ( & mut self , callback : F )
460
+ where
461
+ F : FnMut ( ) + Send + ' static ,
462
+ {
463
+ unsafe {
464
+ let callback_boxed = Box :: new ( callback) ;
465
+ // SAFETY: `Box::into_raw()` always returns a non-null pointer.
466
+ let callback = NonNull :: new_unchecked ( Box :: into_raw ( callback_boxed) ) ;
467
+ let handler = callback. as_ptr ( ) as * mut _ ;
468
+ self . guard . remove_rollback_hook ( ) ;
469
+ self . guard . rollback_hook_callback = Some ( RollbackHookHandler ( callback) ) ;
470
+
471
+ sqlite3_rollback_hook (
472
+ self . as_raw_handle ( ) . as_mut ( ) ,
473
+ Some ( rollback_hook :: < F > ) ,
474
+ handler,
475
+ ) ;
476
+ }
477
+ }
478
+
371
479
/// Removes the progress handler on a database connection. The method does nothing if no handler was set.
372
480
pub fn remove_progress_handler ( & mut self ) {
373
481
self . guard . remove_progress_handler ( ) ;
@@ -376,6 +484,14 @@ impl LockedSqliteHandle<'_> {
376
484
pub fn remove_update_hook ( & mut self ) {
377
485
self . guard . remove_update_hook ( ) ;
378
486
}
487
+
488
+ pub fn remove_commit_hook ( & mut self ) {
489
+ self . guard . remove_commit_hook ( ) ;
490
+ }
491
+
492
+ pub fn remove_rollback_hook ( & mut self ) {
493
+ self . guard . remove_rollback_hook ( ) ;
494
+ }
379
495
}
380
496
381
497
impl Drop for ConnectionState {
@@ -384,6 +500,8 @@ impl Drop for ConnectionState {
384
500
self . statements . clear ( ) ;
385
501
self . remove_progress_handler ( ) ;
386
502
self . remove_update_hook ( ) ;
503
+ self . remove_commit_hook ( ) ;
504
+ self . remove_rollback_hook ( ) ;
387
505
}
388
506
}
389
507
0 commit comments