diff --git a/Cargo.toml b/Cargo.toml index 254f323..3d12d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,9 @@ harness = false [[test]] name = "deny_single_threaded_sqlite_config" +[[test]] +name = "vtab" + [package.metadata.docs.rs] features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace" ] all-features = false diff --git a/src/error.rs b/src/error.rs index d367431..d785c2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -216,12 +216,12 @@ impl error::Error for Error { } } -// These are public but not re-exported by lib.rs, so only visible within crate. - pub fn error_from_sqlite_code(code: c_int, message: Option) -> Error { Error::SqliteFailure(ffi::Error::new(code), message) } +// These are public but not re-exported by lib.rs, so only visible within crate. + pub fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error { let message = if db.is_null() { None diff --git a/src/lib.rs b/src/lib.rs index 38e1751..4a2b18f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; use std::os::raw::{c_int, c_char}; use types::{ToSql, ValueRef}; -use error::{error_from_sqlite_code, error_from_handle}; +use error::error_from_handle; use raw_statement::RawStatement; use cache::StatementCache; @@ -91,7 +91,7 @@ pub use transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior} #[allow(deprecated)] pub use error::SqliteError; -pub use error::Error; +pub use error::{Error, error_from_sqlite_code}; pub use ffi::ErrorCode; pub use cache::CachedStatement; diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index d354325..6a4f5d2 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -45,7 +45,7 @@ use {str_to_cstring, Connection, Error, InnerConnection, Result}; // ffi::sqlite3_vtab => VTab // ffi::sqlite3_vtab_cursor => VTabCursor -pub struct VTabConnection(*mut ffi::sqlite3); +pub struct VTabConnection(pub *mut ffi::sqlite3); impl VTabConnection { /// Get access to the underlying SQLite database connection handle. @@ -110,7 +110,7 @@ bitflags! { } } -pub struct IndexInfo(*mut ffi::sqlite3_index_info); +pub struct IndexInfo(pub *mut ffi::sqlite3_index_info); impl IndexInfo { pub fn constraints(&self) -> IndexConstraintIter { @@ -265,7 +265,7 @@ pub trait VTabCursor: Sized { fn rowid(&self) -> Result; } -pub struct Context(*mut ffi::sqlite3_context); +pub struct Context(pub *mut ffi::sqlite3_context); impl Context { pub fn set_result(&mut self, value: &T) -> Result<()> { @@ -276,7 +276,7 @@ impl Context { } pub struct Values<'a> { - args: &'a [*mut ffi::sqlite3_value], + pub args: &'a [*mut ffi::sqlite3_value], } impl<'a> Values<'a> { @@ -585,6 +585,7 @@ macro_rules! eponymous_module { }; } // eponymous_module macro end +#[macro_export] macro_rules! create_or_connect { ($module:ident, $vtab:ident, $aux:ty, $create_or_connect:ident, $module_func:ident) => { unsafe extern "C" fn $create_or_connect( @@ -598,7 +599,7 @@ macro_rules! create_or_connect { use std::error::Error as StdError; use std::ffi::CStr; use std::slice; - use vtab::mprintf; + use $crate::vtab::mprintf; let mut conn = VTabConnection(db); let aux = aux as *mut $aux; @@ -643,6 +644,7 @@ macro_rules! create_or_connect { }; } // create_or_connect macro end +#[macro_export] macro_rules! common_decl { ( $module:ident, @@ -667,7 +669,7 @@ macro_rules! common_decl { info: *mut ffi::sqlite3_index_info, ) -> c_int { use std::error::Error as StdError; - use vtab::set_err_msg; + use $crate::vtab::set_err_msg; let vt = vtab as *mut $vtab; let mut idx_info = IndexInfo(info); match (*vt).best_index(&mut idx_info) { @@ -695,7 +697,7 @@ macro_rules! common_decl { pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, ) -> c_int { use std::error::Error as StdError; - use vtab::set_err_msg; + use $crate::vtab::set_err_msg; let vt = vtab as *mut $vtab; match (*vt).open() { Ok(cursor) => { @@ -731,7 +733,7 @@ macro_rules! common_decl { use std::ffi::CStr; use std::slice; use std::str; - use vtab::{cursor_error, Values}; + use $crate::vtab::{cursor_error, Values}; let idx_name = if idx_str.is_null() { None } else { @@ -744,7 +746,7 @@ macro_rules! common_decl { cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values)) } unsafe extern "C" fn $next(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int { - use vtab::cursor_error; + use $crate::vtab::cursor_error; let cr = cursor as *mut $cursor; cursor_error(cursor, (*cr).next()) } @@ -757,7 +759,7 @@ macro_rules! common_decl { ctx: *mut ffi::sqlite3_context, i: c_int, ) -> c_int { - use vtab::{result_error, Context}; + use $crate::vtab::{result_error, Context}; let cr = cursor as *mut $cursor; let mut ctxt = Context(ctx); result_error(ctx, (*cr).column(&mut ctxt, i)) @@ -766,7 +768,7 @@ macro_rules! common_decl { cursor: *mut ffi::sqlite3_vtab_cursor, p_rowid: *mut ffi::sqlite3_int64, ) -> c_int { - use vtab::cursor_error; + use $crate::vtab::cursor_error; let cr = cursor as *mut $cursor; match (*cr).rowid() { Ok(rowid) => { @@ -798,7 +800,7 @@ pub unsafe fn cursor_error(cursor: *mut ffi::sqlite3_vtab_cursor, result: Res } /// Virtual tables methods can set an error message by assigning a string to `zErrMsg`. -unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { +pub unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { if !(*vtab).zErrMsg.is_null() { ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void); } @@ -807,7 +809,7 @@ unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { /// To raise an error, the `column` method should use this method to set the error message /// and return the error code. -unsafe fn result_error(ctx: *mut ffi::sqlite3_context, result: Result) -> c_int { +pub unsafe fn result_error(ctx: *mut ffi::sqlite3_context, result: Result) -> c_int { use std::error::Error as StdError; match result { Ok(_) => ffi::SQLITE_OK, diff --git a/tests/vtab.rs b/tests/vtab.rs new file mode 100644 index 0000000..426327c --- /dev/null +++ b/tests/vtab.rs @@ -0,0 +1,127 @@ +//! Ensure Virtual tables can be declared outside `rusqlite` crate. + +#[cfg(feature = "vtab")] +#[macro_use] +extern crate rusqlite; +extern crate libsqlite3_sys as ffi; + +#[cfg(feature = "vtab")] +#[test] +fn test_dummy_module() { + use ffi; + use rusqlite::vtab::{Context, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values}; + use rusqlite::{error_from_sqlite_code, Connection, Error, Result}; + use std::os::raw::{c_char, c_int, c_void}; + + eponymous_module!( + DUMMY_MODULE, + DummyModule, + DummyTab, + (), + DummyTabCursor, + None, + dummy_connect, + dummy_best_index, + dummy_disconnect, + None, + dummy_open, + dummy_close, + dummy_filter, + dummy_next, + dummy_eof, + dummy_column, + dummy_rowid + ); + + #[repr(C)] + struct DummyModule(&'static ffi::sqlite3_module); + + impl Module for DummyModule { + type Aux = (); + type Table = DummyTab; + + fn as_ptr(&self) -> *const ffi::sqlite3_module { + self.0 + } + + fn connect( + _: &mut VTabConnection, + _aux: Option<&()>, + _args: &[&[u8]], + ) -> Result<(String, DummyTab)> { + let vtab = DummyTab { + base: ffi::sqlite3_vtab::default(), + }; + Ok(("CREATE TABLE x(value)".to_owned(), vtab)) + } + } + + #[repr(C)] + struct DummyTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, + } + + impl VTab for DummyTab { + type Cursor = DummyTabCursor; + + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { + info.set_estimated_cost(1.); + Ok(()) + } + + fn open(&self) -> Result { + Ok(DummyTabCursor::default()) + } + } + + #[derive(Default)] + #[repr(C)] + struct DummyTabCursor { + /// Base class. Must be first + base: ffi::sqlite3_vtab_cursor, + /// The rowid + row_id: i64, + } + + impl VTabCursor for DummyTabCursor { + type Table = DummyTab; + + fn vtab(&self) -> &DummyTab { + unsafe { &*(self.base.pVtab as *const DummyTab) } + } + fn filter( + &mut self, + _idx_num: c_int, + _idx_str: Option<&str>, + _args: &Values, + ) -> Result<()> { + self.row_id = 1; + Ok(()) + } + fn next(&mut self) -> Result<()> { + self.row_id += 1; + Ok(()) + } + fn eof(&self) -> bool { + self.row_id > 1 + } + fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> { + ctx.set_result(&self.row_id) + } + fn rowid(&self) -> Result { + Ok(self.row_id) + } + } + + let db = Connection::open_in_memory().unwrap(); + + let module = DummyModule(&DUMMY_MODULE); + + db.create_module("dummy", module, None).unwrap(); + + let mut s = db.prepare("SELECT * FROM dummy()").unwrap(); + + let dummy = s.query_row(&[], |row| row.get::<_, i32>(0)).unwrap(); + assert_eq!(1, dummy); +}