From 3b575c3b4ae3e64bcb71f34e373fd156e15b6b1e Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 25 Apr 2017 20:58:22 +0200 Subject: [PATCH] Implementation of sqlite3_update_hook #260 First draft (no tested and a memory leak) --- .travis.yml | 9 +-- Cargo.toml | 1 + appveyor.yml | 8 +-- src/hooks.rs | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 5 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 src/hooks.rs diff --git a/.travis.yml b/.travis.yml index dd9573f..d6a8990 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,13 +30,14 @@ script: - cargo test --features backup - cargo test --features blob - cargo test --features functions + - cargo test --features hooks - cargo test --features limits - cargo test --features load_extension - cargo test --features trace - cargo test --features chrono - cargo test --features serde_json - cargo test --features bundled - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled" - - cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" + - cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" diff --git a/Cargo.toml b/Cargo.toml index 6e9fd53..65aa744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"] bundled = ["libsqlite3-sys/bundled"] buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"] limits = [] +hooks = [] [dependencies] time = "0.1.0" diff --git a/appveyor.yml b/appveyor.yml index 2d7a1c3..d7801eb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,10 +20,10 @@ build: false test_script: - cargo test --lib --verbose - cargo test --lib --verbose --features bundled - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace buildtime_bindgen" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled" - - cargo test --lib --features "backup blob chrono functions limits load_extension serde_json trace bundled buildtime_bindgen" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled" + - cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen" cache: - C:\Users\appveyor\.cargo diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 0000000..88b08d8 --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,158 @@ +//! Data Change Notification Callbacks +#![allow(non_camel_case_types)] + +use std::mem; +use std::ptr; +use std::os::raw::{c_int, c_char, c_void}; + +use ffi; + +use {Connection, InnerConnection}; + +// Commit And Rollback Notification Callbacks +// http://sqlite.org/c3ref/commit_hook.html +/* +void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +*/ + +/// Authorizer Action Codes +pub enum Action { + UNKNOWN = -1, + SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize, + SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize, + SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize, + SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize, + SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize, + SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize, + SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize, + SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize, + SQLITE_DELETE = ffi::SQLITE_DELETE as isize, + SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize, + SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize, + SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize, + SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize, + SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize, + SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize, + SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize, + SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize, + SQLITE_INSERT = ffi::SQLITE_INSERT as isize, + SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize, + SQLITE_READ = ffi::SQLITE_READ as isize, + SQLITE_SELECT = ffi::SQLITE_SELECT as isize, + SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize, + SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize, + SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize, + SQLITE_DETACH = ffi::SQLITE_DETACH as isize, + SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize, + SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize, + SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize, + SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize, + SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize, + SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize, + SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize, + SQLITE_COPY = ffi::SQLITE_COPY as isize, + SQLITE_RECURSIVE = ffi::SQLITE_RECURSIVE as isize, +} + +impl From for Action { + fn from(code: i32) -> Action { + match code { + ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX, + ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE, + ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX, + ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE, + ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER, + ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW, + ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER, + ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW, + ffi::SQLITE_DELETE => Action::SQLITE_DELETE, + ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX, + ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE, + ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX, + ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE, + ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER, + ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW, + ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER, + ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW, + ffi::SQLITE_INSERT => Action::SQLITE_INSERT, + ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA, + ffi::SQLITE_READ => Action::SQLITE_READ, + ffi::SQLITE_SELECT => Action::SQLITE_SELECT, + ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION, + ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE, + ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH, + ffi::SQLITE_DETACH => Action::SQLITE_DETACH, + ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE, + ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX, + ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE, + ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE, + ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE, + ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION, + ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT, + ffi::SQLITE_COPY => Action::SQLITE_COPY, + ffi::SQLITE_RECURSIVE => Action::SQLITE_RECURSIVE, + _ => Action::UNKNOWN, + } + } +} + +impl Connection { + pub fn update_hook(&self, hook: Option) + where F: FnMut(Action, &str, &str, i64) + { + self.db.borrow_mut().update_hook(hook); + } +} + +impl InnerConnection { + // TODO self.update_hook(None) must be called in InnerConnection#close to free any hook + fn update_hook(&mut self, hook: Option) + where F: FnMut(Action, &str, &str, i64) + { + unsafe extern "C" fn call_boxed_closure(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) + { + use std::ffi::CStr; + use std::str; + + let boxed_hook: *mut F = mem::transmute(p_arg); + assert!(!boxed_hook.is_null(), + "Internal error - null function pointer"); + + let action = Action::from(action_code); + let db_name = { + let c_slice = CStr::from_ptr(db_str).to_bytes(); + str::from_utf8_unchecked(c_slice) + }; + let tbl_name = { + let c_slice = CStr::from_ptr(tbl_str).to_bytes(); + str::from_utf8_unchecked(c_slice) + }; + + (*boxed_hook)(action, db_name, tbl_name, row_id); + } + + let previous_hook = if let Some(hook) = hook { + let boxed_hook: *mut F = Box::into_raw(Box::new(hook)); + unsafe { + ffi::sqlite3_update_hook(self.db(), + Some(call_boxed_closure::), + mem::transmute(boxed_hook)) + } + } else { + unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) } + }; + // TODO Validate: what happens if the previous hook has been set from C ? + if !previous_hook.is_null() { + // free_boxed_value + unsafe { + let _: Box = Box::from_raw(mem::transmute(previous_hook)); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9c0f45a..4e0d741 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,6 +121,8 @@ pub mod functions; pub mod blob; #[cfg(feature = "limits")] pub mod limits; +#[cfg(feature = "hooks")] +pub mod hooks; // Number of cached prepared statements we'll hold on to. const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;