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 #[cfg(feature = "modern_sqlite")]
185 Recursive,
186 }
187
188 impl<'c> AuthAction<'c> {
from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self189 fn from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self {
190 match (code, arg1, arg2) {
191 (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex {
192 index_name,
193 table_name,
194 },
195 (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name },
196 (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => {
197 Self::CreateTempIndex {
198 index_name,
199 table_name,
200 }
201 }
202 (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => {
203 Self::CreateTempTable { table_name }
204 }
205 (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
206 Self::CreateTempTrigger {
207 trigger_name,
208 table_name,
209 }
210 }
211 (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => {
212 Self::CreateTempView { view_name }
213 }
214 (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => {
215 Self::CreateTrigger {
216 trigger_name,
217 table_name,
218 }
219 }
220 (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name },
221 (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name },
222 (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex {
223 index_name,
224 table_name,
225 },
226 (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name },
227 (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => {
228 Self::DropTempIndex {
229 index_name,
230 table_name,
231 }
232 }
233 (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => {
234 Self::DropTempTable { table_name }
235 }
236 (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
237 Self::DropTempTrigger {
238 trigger_name,
239 table_name,
240 }
241 }
242 (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name },
243 (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger {
244 trigger_name,
245 table_name,
246 },
247 (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name },
248 (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name },
249 (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma {
250 pragma_name,
251 pragma_value,
252 },
253 (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read {
254 table_name,
255 column_name,
256 },
257 (ffi::SQLITE_SELECT, ..) => Self::Select,
258 (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction {
259 operation: TransactionOperation::from_str(operation_str),
260 },
261 (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update {
262 table_name,
263 column_name,
264 },
265 (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename },
266 (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name },
267 (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable {
268 database_name,
269 table_name,
270 },
271 (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name },
272 (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name },
273 (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => {
274 Self::CreateVtable {
275 table_name,
276 module_name,
277 }
278 }
279 (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable {
280 table_name,
281 module_name,
282 },
283 (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name },
284 (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint {
285 operation: TransactionOperation::from_str(operation_str),
286 savepoint_name,
287 },
288 #[cfg(feature = "modern_sqlite")] // 3.8.3
289 (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
290 (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
291 }
292 }
293 }
294
295 pub(crate) type BoxedAuthorizer =
296 Box<dyn for<'c> FnMut(AuthContext<'c>) -> Authorization + Send + 'static>;
297
298 /// A transaction operation.
299 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
300 #[non_exhaustive]
301 #[allow(missing_docs)]
302 pub enum TransactionOperation {
303 Unknown,
304 Begin,
305 Release,
306 Rollback,
307 }
308
309 impl TransactionOperation {
from_str(op_str: &str) -> Self310 fn from_str(op_str: &str) -> Self {
311 match op_str {
312 "BEGIN" => Self::Begin,
313 "RELEASE" => Self::Release,
314 "ROLLBACK" => Self::Rollback,
315 _ => Self::Unknown,
316 }
317 }
318 }
319
320 /// [`authorizer`](Connection::authorizer) return code
321 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
322 #[non_exhaustive]
323 pub enum Authorization {
324 /// Authorize the action.
325 Allow,
326 /// Don't allow access, but don't trigger an error either.
327 Ignore,
328 /// Trigger an error.
329 Deny,
330 }
331
332 impl Authorization {
into_raw(self) -> c_int333 fn into_raw(self) -> c_int {
334 match self {
335 Self::Allow => ffi::SQLITE_OK,
336 Self::Ignore => ffi::SQLITE_IGNORE,
337 Self::Deny => ffi::SQLITE_DENY,
338 }
339 }
340 }
341
342 impl Connection {
343 /// Register a callback function to be invoked whenever
344 /// a transaction is committed.
345 ///
346 /// The callback returns `true` to rollback.
347 #[inline]
commit_hook<F>(&self, hook: Option<F>) where F: FnMut() -> bool + Send + 'static,348 pub fn commit_hook<F>(&self, hook: Option<F>)
349 where
350 F: FnMut() -> bool + Send + 'static,
351 {
352 self.db.borrow_mut().commit_hook(hook);
353 }
354
355 /// Register a callback function to be invoked whenever
356 /// a transaction is committed.
357 #[inline]
rollback_hook<F>(&self, hook: Option<F>) where F: FnMut() + Send + 'static,358 pub fn rollback_hook<F>(&self, hook: Option<F>)
359 where
360 F: FnMut() + Send + 'static,
361 {
362 self.db.borrow_mut().rollback_hook(hook);
363 }
364
365 /// Register a callback function to be invoked whenever
366 /// a row is updated, inserted or deleted in a rowid table.
367 ///
368 /// The callback parameters are:
369 ///
370 /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or
371 /// `SQLITE_DELETE`),
372 /// - the name of the database ("main", "temp", ...),
373 /// - the name of the table that is updated,
374 /// - the ROWID of the row that is updated.
375 #[inline]
update_hook<F>(&self, hook: Option<F>) where F: FnMut(Action, &str, &str, i64) + Send + 'static,376 pub fn update_hook<F>(&self, hook: Option<F>)
377 where
378 F: FnMut(Action, &str, &str, i64) + Send + 'static,
379 {
380 self.db.borrow_mut().update_hook(hook);
381 }
382
383 /// Register a query progress callback.
384 ///
385 /// The parameter `num_ops` is the approximate number of virtual machine
386 /// instructions that are evaluated between successive invocations of the
387 /// `handler`. If `num_ops` is less than one then the progress handler
388 /// is disabled.
389 ///
390 /// 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,391 pub fn progress_handler<F>(&self, num_ops: c_int, handler: Option<F>)
392 where
393 F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
394 {
395 self.db.borrow_mut().progress_handler(num_ops, handler);
396 }
397
398 /// Register an authorizer callback that's invoked
399 /// as a statement is being prepared.
400 #[inline]
authorizer<'c, F>(&self, hook: Option<F>) where F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,401 pub fn authorizer<'c, F>(&self, hook: Option<F>)
402 where
403 F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
404 {
405 self.db.borrow_mut().authorizer(hook);
406 }
407 }
408
409 impl InnerConnection {
410 #[inline]
remove_hooks(&mut self)411 pub fn remove_hooks(&mut self) {
412 self.update_hook(None::<fn(Action, &str, &str, i64)>);
413 self.commit_hook(None::<fn() -> bool>);
414 self.rollback_hook(None::<fn()>);
415 self.progress_handler(0, None::<fn() -> bool>);
416 self.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
417 }
418
commit_hook<F>(&mut self, hook: Option<F>) where F: FnMut() -> bool + Send + 'static,419 fn commit_hook<F>(&mut self, hook: Option<F>)
420 where
421 F: FnMut() -> bool + Send + 'static,
422 {
423 unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
424 where
425 F: FnMut() -> bool,
426 {
427 let r = catch_unwind(|| {
428 let boxed_hook: *mut F = p_arg.cast::<F>();
429 (*boxed_hook)()
430 });
431 if let Ok(true) = r {
432 1
433 } else {
434 0
435 }
436 }
437
438 // unlike `sqlite3_create_function_v2`, we cannot specify a `xDestroy` with
439 // `sqlite3_commit_hook`. so we keep the `xDestroy` function in
440 // `InnerConnection.free_boxed_hook`.
441 let free_commit_hook = if hook.is_some() {
442 Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
443 } else {
444 None
445 };
446
447 let previous_hook = match hook {
448 Some(hook) => {
449 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
450 unsafe {
451 ffi::sqlite3_commit_hook(
452 self.db(),
453 Some(call_boxed_closure::<F>),
454 boxed_hook.cast(),
455 )
456 }
457 }
458 _ => unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) },
459 };
460 if !previous_hook.is_null() {
461 if let Some(free_boxed_hook) = self.free_commit_hook {
462 unsafe { free_boxed_hook(previous_hook) };
463 }
464 }
465 self.free_commit_hook = free_commit_hook;
466 }
467
rollback_hook<F>(&mut self, hook: Option<F>) where F: FnMut() + Send + 'static,468 fn rollback_hook<F>(&mut self, hook: Option<F>)
469 where
470 F: FnMut() + Send + 'static,
471 {
472 unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
473 where
474 F: FnMut(),
475 {
476 drop(catch_unwind(|| {
477 let boxed_hook: *mut F = p_arg.cast::<F>();
478 (*boxed_hook)();
479 }));
480 }
481
482 let free_rollback_hook = if hook.is_some() {
483 Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
484 } else {
485 None
486 };
487
488 let previous_hook = match hook {
489 Some(hook) => {
490 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
491 unsafe {
492 ffi::sqlite3_rollback_hook(
493 self.db(),
494 Some(call_boxed_closure::<F>),
495 boxed_hook.cast(),
496 )
497 }
498 }
499 _ => unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) },
500 };
501 if !previous_hook.is_null() {
502 if let Some(free_boxed_hook) = self.free_rollback_hook {
503 unsafe { free_boxed_hook(previous_hook) };
504 }
505 }
506 self.free_rollback_hook = free_rollback_hook;
507 }
508
update_hook<F>(&mut self, hook: Option<F>) where F: FnMut(Action, &str, &str, i64) + Send + 'static,509 fn update_hook<F>(&mut self, hook: Option<F>)
510 where
511 F: FnMut(Action, &str, &str, i64) + Send + 'static,
512 {
513 unsafe extern "C" fn call_boxed_closure<F>(
514 p_arg: *mut c_void,
515 action_code: c_int,
516 p_db_name: *const c_char,
517 p_table_name: *const c_char,
518 row_id: i64,
519 ) where
520 F: FnMut(Action, &str, &str, i64),
521 {
522 let action = Action::from(action_code);
523 drop(catch_unwind(|| {
524 let boxed_hook: *mut F = p_arg.cast::<F>();
525 (*boxed_hook)(
526 action,
527 expect_utf8(p_db_name, "database name"),
528 expect_utf8(p_table_name, "table name"),
529 row_id,
530 );
531 }));
532 }
533
534 let free_update_hook = if hook.is_some() {
535 Some(free_boxed_hook::<F> as unsafe fn(*mut c_void))
536 } else {
537 None
538 };
539
540 let previous_hook = match hook {
541 Some(hook) => {
542 let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
543 unsafe {
544 ffi::sqlite3_update_hook(
545 self.db(),
546 Some(call_boxed_closure::<F>),
547 boxed_hook.cast(),
548 )
549 }
550 }
551 _ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
552 };
553 if !previous_hook.is_null() {
554 if let Some(free_boxed_hook) = self.free_update_hook {
555 unsafe { free_boxed_hook(previous_hook) };
556 }
557 }
558 self.free_update_hook = free_update_hook;
559 }
560
progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>) where F: FnMut() -> bool + Send + RefUnwindSafe + 'static,561 fn progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>)
562 where
563 F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
564 {
565 unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
566 where
567 F: FnMut() -> bool,
568 {
569 let r = catch_unwind(|| {
570 let boxed_handler: *mut F = p_arg.cast::<F>();
571 (*boxed_handler)()
572 });
573 if let Ok(true) = r {
574 1
575 } else {
576 0
577 }
578 }
579
580 if let Some(handler) = handler {
581 let boxed_handler = Box::new(handler);
582 unsafe {
583 ffi::sqlite3_progress_handler(
584 self.db(),
585 num_ops,
586 Some(call_boxed_closure::<F>),
587 &*boxed_handler as *const F as *mut _,
588 );
589 }
590 self.progress_handler = Some(boxed_handler);
591 } else {
592 unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
593 self.progress_handler = None;
594 };
595 }
596
authorizer<'c, F>(&'c mut self, authorizer: Option<F>) where F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,597 fn authorizer<'c, F>(&'c mut self, authorizer: Option<F>)
598 where
599 F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
600 {
601 unsafe extern "C" fn call_boxed_closure<'c, F>(
602 p_arg: *mut c_void,
603 action_code: c_int,
604 param1: *const c_char,
605 param2: *const c_char,
606 db_name: *const c_char,
607 trigger_or_view_name: *const c_char,
608 ) -> c_int
609 where
610 F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static,
611 {
612 catch_unwind(|| {
613 let action = AuthAction::from_raw(
614 action_code,
615 expect_optional_utf8(param1, "authorizer param 1"),
616 expect_optional_utf8(param2, "authorizer param 2"),
617 );
618 let auth_ctx = AuthContext {
619 action,
620 database_name: expect_optional_utf8(db_name, "database name"),
621 accessor: expect_optional_utf8(
622 trigger_or_view_name,
623 "accessor (inner-most trigger or view)",
624 ),
625 };
626 let boxed_hook: *mut F = p_arg.cast::<F>();
627 (*boxed_hook)(auth_ctx)
628 })
629 .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw)
630 }
631
632 let callback_fn = authorizer
633 .as_ref()
634 .map(|_| call_boxed_closure::<'c, F> as unsafe extern "C" fn(_, _, _, _, _, _) -> _);
635 let boxed_authorizer = authorizer.map(Box::new);
636
637 match unsafe {
638 ffi::sqlite3_set_authorizer(
639 self.db(),
640 callback_fn,
641 boxed_authorizer
642 .as_ref()
643 .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _),
644 )
645 } {
646 ffi::SQLITE_OK => {
647 self.authorizer = boxed_authorizer.map(|ba| ba as _);
648 }
649 err_code => {
650 // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE`
651 // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid.
652 // This library does not allow constructing a null db ptr, so if this branch
653 // is hit, something very bad has happened. Panicking instead of returning
654 // `Result` keeps this hook's API consistent with the others.
655 panic!("unexpectedly failed to set_authorizer: {}", unsafe {
656 crate::error::error_from_handle(self.db(), err_code)
657 });
658 }
659 }
660 }
661 }
662
free_boxed_hook<F>(p: *mut c_void)663 unsafe fn free_boxed_hook<F>(p: *mut c_void) {
664 drop(Box::from_raw(p.cast::<F>()));
665 }
666
expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str667 unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
668 expect_optional_utf8(p_str, description)
669 .unwrap_or_else(|| panic!("received empty {}", description))
670 }
671
expect_optional_utf8<'a>( p_str: *const c_char, description: &'static str, ) -> Option<&'a str>672 unsafe fn expect_optional_utf8<'a>(
673 p_str: *const c_char,
674 description: &'static str,
675 ) -> Option<&'a str> {
676 if p_str.is_null() {
677 return None;
678 }
679 std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
680 .unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
681 .into()
682 }
683
684 #[cfg(test)]
685 mod test {
686 use super::Action;
687 use crate::{Connection, Result};
688 use std::sync::atomic::{AtomicBool, Ordering};
689
690 #[test]
test_commit_hook() -> Result<()>691 fn test_commit_hook() -> Result<()> {
692 let db = Connection::open_in_memory()?;
693
694 static CALLED: AtomicBool = AtomicBool::new(false);
695 db.commit_hook(Some(|| {
696 CALLED.store(true, Ordering::Relaxed);
697 false
698 }));
699 db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
700 assert!(CALLED.load(Ordering::Relaxed));
701 Ok(())
702 }
703
704 #[test]
test_fn_commit_hook() -> Result<()>705 fn test_fn_commit_hook() -> Result<()> {
706 let db = Connection::open_in_memory()?;
707
708 fn hook() -> bool {
709 true
710 }
711
712 db.commit_hook(Some(hook));
713 db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
714 .unwrap_err();
715 Ok(())
716 }
717
718 #[test]
test_rollback_hook() -> Result<()>719 fn test_rollback_hook() -> Result<()> {
720 let db = Connection::open_in_memory()?;
721
722 static CALLED: AtomicBool = AtomicBool::new(false);
723 db.rollback_hook(Some(|| {
724 CALLED.store(true, Ordering::Relaxed);
725 }));
726 db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
727 assert!(CALLED.load(Ordering::Relaxed));
728 Ok(())
729 }
730
731 #[test]
test_update_hook() -> Result<()>732 fn test_update_hook() -> Result<()> {
733 let db = Connection::open_in_memory()?;
734
735 static CALLED: AtomicBool = AtomicBool::new(false);
736 db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
737 assert_eq!(Action::SQLITE_INSERT, action);
738 assert_eq!("main", db);
739 assert_eq!("foo", tbl);
740 assert_eq!(1, row_id);
741 CALLED.store(true, Ordering::Relaxed);
742 }));
743 db.execute_batch("CREATE TABLE foo (t TEXT)")?;
744 db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
745 assert!(CALLED.load(Ordering::Relaxed));
746 Ok(())
747 }
748
749 #[test]
test_progress_handler() -> Result<()>750 fn test_progress_handler() -> Result<()> {
751 let db = Connection::open_in_memory()?;
752
753 static CALLED: AtomicBool = AtomicBool::new(false);
754 db.progress_handler(
755 1,
756 Some(|| {
757 CALLED.store(true, Ordering::Relaxed);
758 false
759 }),
760 );
761 db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
762 assert!(CALLED.load(Ordering::Relaxed));
763 Ok(())
764 }
765
766 #[test]
test_progress_handler_interrupt() -> Result<()>767 fn test_progress_handler_interrupt() -> Result<()> {
768 let db = Connection::open_in_memory()?;
769
770 fn handler() -> bool {
771 true
772 }
773
774 db.progress_handler(1, Some(handler));
775 db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
776 .unwrap_err();
777 Ok(())
778 }
779
780 #[test]
test_authorizer() -> Result<()>781 fn test_authorizer() -> Result<()> {
782 use super::{AuthAction, AuthContext, Authorization};
783
784 let db = Connection::open_in_memory()?;
785 db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")
786 .unwrap();
787
788 let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
789 AuthAction::Read { column_name, .. } if column_name == "private" => {
790 Authorization::Ignore
791 }
792 AuthAction::DropTable { .. } => Authorization::Deny,
793 AuthAction::Pragma { .. } => panic!("shouldn't be called"),
794 _ => Authorization::Allow,
795 };
796
797 db.authorizer(Some(authorizer));
798 db.execute_batch(
799 "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;",
800 )
801 .unwrap();
802 db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> {
803 assert_eq!(row.get::<_, String>("public")?, "pub txt");
804 assert!(row.get::<_, Option<String>>("private")?.is_none());
805 Ok(())
806 })
807 .unwrap();
808 db.execute_batch("DROP TABLE foo").unwrap_err();
809
810 db.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
811 db.execute_batch("PRAGMA user_version=1").unwrap(); // Disallowed by first authorizer, but it's now removed.
812
813 Ok(())
814 }
815 }
816