1 //! Commit, Data Change and Rollback Notification Callbacks
2 #![allow(non_camel_case_types)]
3 
4 use std::os::raw::{c_char, c_int, c_void};
5 use std::panic::{catch_unwind, RefUnwindSafe};
6 use std::ptr;
7 
8 use crate::ffi;
9 
10 use crate::{Connection, InnerConnection};
11 
12 /// Action Codes
13 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
14 #[repr(i32)]
15 #[non_exhaustive]
16 #[allow(clippy::upper_case_acronyms)]
17 pub enum Action {
18     /// Unsupported / unexpected action
19     UNKNOWN = -1,
20     /// DELETE command
21     SQLITE_DELETE = ffi::SQLITE_DELETE,
22     /// INSERT command
23     SQLITE_INSERT = ffi::SQLITE_INSERT,
24     /// UPDATE command
25     SQLITE_UPDATE = ffi::SQLITE_UPDATE,
26 }
27 
28 impl From<i32> for Action {
29     #[inline]
from(code: i32) -> Action30     fn from(code: i32) -> Action {
31         match code {
32             ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
33             ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
34             ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
35             _ => Action::UNKNOWN,
36         }
37     }
38 }
39 
40 /// The context received by an authorizer hook.
41 ///
42 /// See <https://sqlite.org/c3ref/set_authorizer.html> for more info.
43 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
44 pub struct AuthContext<'c> {
45     /// The action to be authorized.
46     pub action: AuthAction<'c>,
47 
48     /// The database name, if applicable.
49     pub database_name: Option<&'c str>,
50 
51     /// The inner-most trigger or view responsible for the access attempt.
52     /// `None` if the access attempt was made by top-level SQL code.
53     pub accessor: Option<&'c str>,
54 }
55 
56 /// Actions and arguments found within a statement during
57 /// preparation.
58 ///
59 /// See <https://sqlite.org/c3ref/c_alter_table.html> for more info.
60 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
61 #[non_exhaustive]
62 #[allow(missing_docs)]
63 pub enum AuthAction<'c> {
64     /// This variant is not normally produced by SQLite. You may encounter it
65     // if you're using a different version than what's supported by this library.
66     Unknown {
67         /// The unknown authorization action code.
68         code: i32,
69         /// The third arg to the authorizer callback.
70         arg1: Option<&'c str>,
71         /// The fourth arg to the authorizer callback.
72         arg2: Option<&'c str>,
73     },
74     CreateIndex {
75         index_name: &'c str,
76         table_name: &'c str,
77     },
78     CreateTable {
79         table_name: &'c str,
80     },
81     CreateTempIndex {
82         index_name: &'c str,
83         table_name: &'c str,
84     },
85     CreateTempTable {
86         table_name: &'c str,
87     },
88     CreateTempTrigger {
89         trigger_name: &'c str,
90         table_name: &'c str,
91     },
92     CreateTempView {
93         view_name: &'c str,
94     },
95     CreateTrigger {
96         trigger_name: &'c str,
97         table_name: &'c str,
98     },
99     CreateView {
100         view_name: &'c str,
101     },
102     Delete {
103         table_name: &'c str,
104     },
105     DropIndex {
106         index_name: &'c str,
107         table_name: &'c str,
108     },
109     DropTable {
110         table_name: &'c str,
111     },
112     DropTempIndex {
113         index_name: &'c str,
114         table_name: &'c str,
115     },
116     DropTempTable {
117         table_name: &'c str,
118     },
119     DropTempTrigger {
120         trigger_name: &'c str,
121         table_name: &'c str,
122     },
123     DropTempView {
124         view_name: &'c str,
125     },
126     DropTrigger {
127         trigger_name: &'c str,
128         table_name: &'c str,
129     },
130     DropView {
131         view_name: &'c str,
132     },
133     Insert {
134         table_name: &'c str,
135     },
136     Pragma {
137         pragma_name: &'c str,
138         /// The pragma value, if present (e.g., `PRAGMA name = value;`).
139         pragma_value: Option<&'c str>,
140     },
141     Read {
142         table_name: &'c str,
143         column_name: &'c str,
144     },
145     Select,
146     Transaction {
147         operation: TransactionOperation,
148     },
149     Update {
150         table_name: &'c str,
151         column_name: &'c str,
152     },
153     Attach {
154         filename: &'c str,
155     },
156     Detach {
157         database_name: &'c str,
158     },
159     AlterTable {
160         database_name: &'c str,
161         table_name: &'c str,
162     },
163     Reindex {
164         index_name: &'c str,
165     },
166     Analyze {
167         table_name: &'c str,
168     },
169     CreateVtable {
170         table_name: &'c str,
171         module_name: &'c str,
172     },
173     DropVtable {
174         table_name: &'c str,
175         module_name: &'c str,
176     },
177     Function {
178         function_name: &'c str,
179     },
180     Savepoint {
181         operation: TransactionOperation,
182         savepoint_name: &'c str,
183     },
184     Recursive,
185 }
186 
187 impl<'c> AuthAction<'c> {
from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self188     fn from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self {
189         match (code, arg1, arg2) {
190             (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex {
191                 index_name,
192                 table_name,
193             },
194             (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name },
195             (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => {
196                 Self::CreateTempIndex {
197                     index_name,
198                     table_name,
199                 }
200             }
201             (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => {
202                 Self::CreateTempTable { table_name }
203             }
204             (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
205                 Self::CreateTempTrigger {
206                     trigger_name,
207                     table_name,
208                 }
209             }
210             (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => {
211                 Self::CreateTempView { view_name }
212             }
213             (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => {
214                 Self::CreateTrigger {
215                     trigger_name,
216                     table_name,
217                 }
218             }
219             (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name },
220             (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name },
221             (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex {
222                 index_name,
223                 table_name,
224             },
225             (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name },
226             (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => {
227                 Self::DropTempIndex {
228                     index_name,
229                     table_name,
230                 }
231             }
232             (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => {
233                 Self::DropTempTable { table_name }
234             }
235             (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
236                 Self::DropTempTrigger {
237                     trigger_name,
238                     table_name,
239                 }
240             }
241             (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name },
242             (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger {
243                 trigger_name,
244                 table_name,
245             },
246             (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name },
247             (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name },
248             (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma {
249                 pragma_name,
250                 pragma_value,
251             },
252             (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read {
253                 table_name,
254                 column_name,
255             },
256             (ffi::SQLITE_SELECT, ..) => Self::Select,
257             (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction {
258                 operation: TransactionOperation::from_str(operation_str),
259             },
260             (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update {
261                 table_name,
262                 column_name,
263             },
264             (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename },
265             (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name },
266             (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable {
267                 database_name,
268                 table_name,
269             },
270             (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name },
271             (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name },
272             (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => {
273                 Self::CreateVtable {
274                     table_name,
275                     module_name,
276                 }
277             }
278             (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable {
279                 table_name,
280                 module_name,
281             },
282             (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name },
283             (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint {
284                 operation: TransactionOperation::from_str(operation_str),
285                 savepoint_name,
286             },
287             (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
288             (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
289         }
290     }
291 }
292 
293 pub(crate) type BoxedAuthorizer =
294     Box<dyn for<'c> FnMut(AuthContext<'c>) -> Authorization + Send + 'static>;
295 
296 /// A transaction operation.
297 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
298 #[non_exhaustive]
299 #[allow(missing_docs)]
300 pub enum TransactionOperation {
301     Unknown,
302     Begin,
303     Release,
304     Rollback,
305 }
306 
307 impl TransactionOperation {
from_str(op_str: &str) -> Self308     fn from_str(op_str: &str) -> Self {
309         match op_str {
310             "BEGIN" => Self::Begin,
311             "RELEASE" => Self::Release,
312             "ROLLBACK" => Self::Rollback,
313             _ => Self::Unknown,
314         }
315     }
316 }
317 
318 /// [`authorizer`](Connection::authorizer) return code
319 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
320 #[non_exhaustive]
321 pub enum Authorization {
322     /// Authorize the action.
323     Allow,
324     /// Don't allow access, but don't trigger an error either.
325     Ignore,
326     /// Trigger an error.
327     Deny,
328 }
329 
330 impl Authorization {
into_raw(self) -> c_int331     fn into_raw(self) -> c_int {
332         match self {
333             Self::Allow => ffi::SQLITE_OK,
334             Self::Ignore => ffi::SQLITE_IGNORE,
335             Self::Deny => ffi::SQLITE_DENY,
336         }
337     }
338 }
339 
340 impl Connection {
341     /// Register a callback function to be invoked whenever
342     /// a transaction is committed.
343     ///
344     /// The callback returns `true` to rollback.
345     #[inline]
commit_hook<F>(&self, hook: Option<F>) where F: FnMut() -> bool + Send + 'static,346     pub fn commit_hook<F>(&self, hook: Option<F>)
347     where
348         F: FnMut() -> bool + Send + 'static,
349     {
350         self.db.borrow_mut().commit_hook(hook);
351     }
352 
353     /// Register a callback function to be invoked whenever
354     /// a transaction is committed.
355     #[inline]
rollback_hook<F>(&self, hook: Option<F>) where F: FnMut() + Send + 'static,356     pub fn rollback_hook<F>(&self, hook: Option<F>)
357     where
358         F: FnMut() + Send + 'static,
359     {
360         self.db.borrow_mut().rollback_hook(hook);
361     }
362 
363     /// Register a callback function to be invoked whenever
364     /// a row is updated, inserted or deleted in a rowid table.
365     ///
366     /// The callback parameters are:
367     ///
368     /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or
369     /// `SQLITE_DELETE`),
370     /// - the name of the database ("main", "temp", ...),
371     /// - the name of the table that is updated,
372     /// - the ROWID of the row that is updated.
373     #[inline]
update_hook<F>(&self, hook: Option<F>) where F: FnMut(Action, &str, &str, i64) + Send + 'static,374     pub fn update_hook<F>(&self, hook: Option<F>)
375     where
376         F: FnMut(Action, &str, &str, i64) + Send + 'static,
377     {
378         self.db.borrow_mut().update_hook(hook);
379     }
380 
381     /// Register a query progress callback.
382     ///
383     /// The parameter `num_ops` is the approximate number of virtual machine
384     /// instructions that are evaluated between successive invocations of the
385     /// `handler`. If `num_ops` is less than one then the progress handler
386     /// is disabled.
387     ///
388     /// If the progress callback returns `true`, the operation is interrupted.
progress_handler<F>(&self, num_ops: c_int, handler: Option<F>) where F: FnMut() -> bool + Send + RefUnwindSafe + 'static,389     pub fn progress_handler<F>(&self, num_ops: c_int, handler: Option<F>)
390     where
391         F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
392     {
393         self.db.borrow_mut().progress_handler(num_ops, handler);
394     }
395 
396     /// Register an authorizer callback that's invoked
397     /// as a statement is being prepared.
398     #[inline]
authorizer<'c, F>(&self, hook: Option<F>) where F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,399     pub fn authorizer<'c, F>(&self, hook: Option<F>)
400     where
401         F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
402     {
403         self.db.borrow_mut().authorizer(hook);
404     }
405 }
406 
407 impl InnerConnection {
408     #[inline]
remove_hooks(&mut self)409     pub fn remove_hooks(&mut self) {
410         self.update_hook(None::<fn(Action, &str, &str, i64)>);
411         self.commit_hook(None::<fn() -> bool>);
412         self.rollback_hook(None::<fn()>);
413         self.progress_handler(0, None::<fn() -> bool>);
414         self.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
415     }
416 
commit_hook<F>(&mut self, hook: Option<F>) where F: FnMut() -> bool + Send + 'static,417     fn commit_hook<F>(&mut self, hook: Option<F>)
418     where
419         F: FnMut() -> bool + Send + 'static,
420     {
421         unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
422         where
423             F: FnMut() -> bool,
424         {
425             let r = catch_unwind(|| {
426                 let boxed_hook: *mut F = p_arg.cast::<F>();
427                 (*boxed_hook)()
428             });
429             c_int::from(r.unwrap_or_default())
430         }
431 
432         // unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with
433         // `sqlite3_commit_hook`. so we keep the `xDestroy` function in
434         // `InnerConnection.free_boxed_hook`.
435         let free_commit_hook = if hook.is_some() {
436             Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
437         } else {
438             None
439         };
440 
441         let previous_hook = match hook {
442             Some(hook) => {
443                 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
444                 unsafe {
445                     ffi::sqlite3_commit_hook(
446                         self.db(),
447                         Some(call_boxed_closure::<F>),
448                         boxed_hook.cast(),
449                     )
450                 }
451             }
452             _ => unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) },
453         };
454         if !previous_hook.is_null() {
455             if let Some(free_boxed_hook) = self.free_commit_hook {
456                 unsafe { free_boxed_hook(previous_hook) };
457             }
458         }
459         self.free_commit_hook = free_commit_hook;
460     }
461 
rollback_hook<F>(&mut self, hook: Option<F>) where F: FnMut() + Send + 'static,462     fn rollback_hook<F>(&mut self, hook: Option<F>)
463     where
464         F: FnMut() + Send + 'static,
465     {
466         unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
467         where
468             F: FnMut(),
469         {
470             drop(catch_unwind(|| {
471                 let boxed_hook: *mut F = p_arg.cast::<F>();
472                 (*boxed_hook)();
473             }));
474         }
475 
476         let free_rollback_hook = if hook.is_some() {
477             Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
478         } else {
479             None
480         };
481 
482         let previous_hook = match hook {
483             Some(hook) => {
484                 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
485                 unsafe {
486                     ffi::sqlite3_rollback_hook(
487                         self.db(),
488                         Some(call_boxed_closure::<F>),
489                         boxed_hook.cast(),
490                     )
491                 }
492             }
493             _ => unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) },
494         };
495         if !previous_hook.is_null() {
496             if let Some(free_boxed_hook) = self.free_rollback_hook {
497                 unsafe { free_boxed_hook(previous_hook) };
498             }
499         }
500         self.free_rollback_hook = free_rollback_hook;
501     }
502 
update_hook<F>(&mut self, hook: Option<F>) where F: FnMut(Action, &str, &str, i64) + Send + 'static,503     fn update_hook<F>(&mut self, hook: Option<F>)
504     where
505         F: FnMut(Action, &str, &str, i64) + Send + 'static,
506     {
507         unsafe extern "C" fn call_boxed_closure<F>(
508             p_arg: *mut c_void,
509             action_code: c_int,
510             p_db_name: *const c_char,
511             p_table_name: *const c_char,
512             row_id: i64,
513         ) where
514             F: FnMut(Action, &str, &str, i64),
515         {
516             let action = Action::from(action_code);
517             drop(catch_unwind(|| {
518                 let boxed_hook: *mut F = p_arg.cast::<F>();
519                 (*boxed_hook)(
520                     action,
521                     expect_utf8(p_db_name, "database name"),
522                     expect_utf8(p_table_name, "table name"),
523                     row_id,
524                 );
525             }));
526         }
527 
528         let free_update_hook = if hook.is_some() {
529             Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
530         } else {
531             None
532         };
533 
534         let previous_hook = match hook {
535             Some(hook) => {
536                 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
537                 unsafe {
538                     ffi::sqlite3_update_hook(
539                         self.db(),
540                         Some(call_boxed_closure::<F>),
541                         boxed_hook.cast(),
542                     )
543                 }
544             }
545             _ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
546         };
547         if !previous_hook.is_null() {
548             if let Some(free_boxed_hook) = self.free_update_hook {
549                 unsafe { free_boxed_hook(previous_hook) };
550             }
551         }
552         self.free_update_hook = free_update_hook;
553     }
554 
progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>) where F: FnMut() -> bool + Send + RefUnwindSafe + 'static,555     fn progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>)
556     where
557         F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
558     {
559         unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
560         where
561             F: FnMut() -> bool,
562         {
563             let r = catch_unwind(|| {
564                 let boxed_handler: *mut F = p_arg.cast::<F>();
565                 (*boxed_handler)()
566             });
567             c_int::from(r.unwrap_or_default())
568         }
569 
570         if let Some(handler) = handler {
571             let boxed_handler = Box::new(handler);
572             unsafe {
573                 ffi::sqlite3_progress_handler(
574                     self.db(),
575                     num_ops,
576                     Some(call_boxed_closure::<F>),
577                     &*boxed_handler as *const F as *mut _,
578                 );
579             }
580             self.progress_handler = Some(boxed_handler);
581         } else {
582             unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
583             self.progress_handler = None;
584         };
585     }
586 
authorizer<'c, F>(&'c mut self, authorizer: Option<F>) where F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,587     fn authorizer<'c, F>(&'c mut self, authorizer: Option<F>)
588     where
589         F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
590     {
591         unsafe extern "C" fn call_boxed_closure<'c, F>(
592             p_arg: *mut c_void,
593             action_code: c_int,
594             param1: *const c_char,
595             param2: *const c_char,
596             db_name: *const c_char,
597             trigger_or_view_name: *const c_char,
598         ) -> c_int
599         where
600             F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static,
601         {
602             catch_unwind(|| {
603                 let action = AuthAction::from_raw(
604                     action_code,
605                     expect_optional_utf8(param1, "authorizer param 1"),
606                     expect_optional_utf8(param2, "authorizer param 2"),
607                 );
608                 let auth_ctx = AuthContext {
609                     action,
610                     database_name: expect_optional_utf8(db_name, "database name"),
611                     accessor: expect_optional_utf8(
612                         trigger_or_view_name,
613                         "accessor (inner-most trigger or view)",
614                     ),
615                 };
616                 let boxed_hook: *mut F = p_arg.cast::<F>();
617                 (*boxed_hook)(auth_ctx)
618             })
619             .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw)
620         }
621 
622         let callback_fn = authorizer
623             .as_ref()
624             .map(|_| call_boxed_closure::<'c, F> as unsafe extern "C" fn(_, _, _, _, _, _) -> _);
625         let boxed_authorizer = authorizer.map(Box::new);
626 
627         match unsafe {
628             ffi::sqlite3_set_authorizer(
629                 self.db(),
630                 callback_fn,
631                 boxed_authorizer
632                     .as_ref()
633                     .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _),
634             )
635         } {
636             ffi::SQLITE_OK => {
637                 self.authorizer = boxed_authorizer.map(|ba| ba as _);
638             }
639             err_code => {
640                 // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE`
641                 // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid.
642                 // This library does not allow constructing a null db ptr, so if this branch
643                 // is hit, something very bad has happened. Panicking instead of returning
644                 // `Result` keeps this hook's API consistent with the others.
645                 panic!("unexpectedly failed to set_authorizer: {}", unsafe {
646                     crate::error::error_from_handle(self.db(), err_code)
647                 });
648             }
649         }
650     }
651 }
652 
free_boxed_hook<F>(p: *mut c_void)653 unsafe fn free_boxed_hook<F>(p: *mut c_void) {
654     drop(Box::from_raw(p.cast::<F>()));
655 }
656 
expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str657 unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
658     expect_optional_utf8(p_str, description)
659         .unwrap_or_else(|| panic!("received empty {}", description))
660 }
661 
expect_optional_utf8<'a>( p_str: *const c_char, description: &'static str, ) -> Option<&'a str>662 unsafe fn expect_optional_utf8<'a>(
663     p_str: *const c_char,
664     description: &'static str,
665 ) -> Option<&'a str> {
666     if p_str.is_null() {
667         return None;
668     }
669     std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
670         .unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
671         .into()
672 }
673 
674 #[cfg(test)]
675 mod test {
676     use super::Action;
677     use crate::{Connection, Result};
678     use std::sync::atomic::{AtomicBool, Ordering};
679 
680     #[test]
test_commit_hook() -> Result<()>681     fn test_commit_hook() -> Result<()> {
682         let db = Connection::open_in_memory()?;
683 
684         static CALLED: AtomicBool = AtomicBool::new(false);
685         db.commit_hook(Some(|| {
686             CALLED.store(true, Ordering::Relaxed);
687             false
688         }));
689         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
690         assert!(CALLED.load(Ordering::Relaxed));
691         Ok(())
692     }
693 
694     #[test]
test_fn_commit_hook() -> Result<()>695     fn test_fn_commit_hook() -> Result<()> {
696         let db = Connection::open_in_memory()?;
697 
698         fn hook() -> bool {
699             true
700         }
701 
702         db.commit_hook(Some(hook));
703         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
704             .unwrap_err();
705         Ok(())
706     }
707 
708     #[test]
test_rollback_hook() -> Result<()>709     fn test_rollback_hook() -> Result<()> {
710         let db = Connection::open_in_memory()?;
711 
712         static CALLED: AtomicBool = AtomicBool::new(false);
713         db.rollback_hook(Some(|| {
714             CALLED.store(true, Ordering::Relaxed);
715         }));
716         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
717         assert!(CALLED.load(Ordering::Relaxed));
718         Ok(())
719     }
720 
721     #[test]
test_update_hook() -> Result<()>722     fn test_update_hook() -> Result<()> {
723         let db = Connection::open_in_memory()?;
724 
725         static CALLED: AtomicBool = AtomicBool::new(false);
726         db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
727             assert_eq!(Action::SQLITE_INSERT, action);
728             assert_eq!("main", db);
729             assert_eq!("foo", tbl);
730             assert_eq!(1, row_id);
731             CALLED.store(true, Ordering::Relaxed);
732         }));
733         db.execute_batch("CREATE TABLE foo (t TEXT)")?;
734         db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
735         assert!(CALLED.load(Ordering::Relaxed));
736         Ok(())
737     }
738 
739     #[test]
test_progress_handler() -> Result<()>740     fn test_progress_handler() -> Result<()> {
741         let db = Connection::open_in_memory()?;
742 
743         static CALLED: AtomicBool = AtomicBool::new(false);
744         db.progress_handler(
745             1,
746             Some(|| {
747                 CALLED.store(true, Ordering::Relaxed);
748                 false
749             }),
750         );
751         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
752         assert!(CALLED.load(Ordering::Relaxed));
753         Ok(())
754     }
755 
756     #[test]
test_progress_handler_interrupt() -> Result<()>757     fn test_progress_handler_interrupt() -> Result<()> {
758         let db = Connection::open_in_memory()?;
759 
760         fn handler() -> bool {
761             true
762         }
763 
764         db.progress_handler(1, Some(handler));
765         db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
766             .unwrap_err();
767         Ok(())
768     }
769 
770     #[test]
test_authorizer() -> Result<()>771     fn test_authorizer() -> Result<()> {
772         use super::{AuthAction, AuthContext, Authorization};
773 
774         let db = Connection::open_in_memory()?;
775         db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")
776             .unwrap();
777 
778         let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
779             AuthAction::Read { column_name, .. } if column_name == "private" => {
780                 Authorization::Ignore
781             }
782             AuthAction::DropTable { .. } => Authorization::Deny,
783             AuthAction::Pragma { .. } => panic!("shouldn't be called"),
784             _ => Authorization::Allow,
785         };
786 
787         db.authorizer(Some(authorizer));
788         db.execute_batch(
789             "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;",
790         )
791         .unwrap();
792         db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> {
793             assert_eq!(row.get::<_, String>("public")?, "pub txt");
794             assert!(row.get::<_, Option<String>>("private")?.is_none());
795             Ok(())
796         })
797         .unwrap();
798         db.execute_batch("DROP TABLE foo").unwrap_err();
799 
800         db.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
801         db.execute_batch("PRAGMA user_version=1").unwrap(); // Disallowed by first authorizer, but it's now removed.
802 
803         Ok(())
804     }
805 }
806