From e2df03f47434a779efb6aa89758b50eda98879d2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 14 Jul 2018 18:47:52 +0200 Subject: [PATCH] Remove macros --- Cargo.toml | 5 +- src/error.rs | 4 +- src/lib.rs | 6 +- src/vtab/array.rs | 64 ++--- src/vtab/csvtab.rs | 95 +++---- src/vtab/mod.rs | 654 ++++++++++++++++++++++----------------------- src/vtab/series.rs | 72 ++--- tests/vtab.rs | 54 +--- 8 files changed, 406 insertions(+), 548 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d12d63..156f053 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ limits = [] hooks = [] sqlcipher = ["libsqlite3-sys/sqlcipher"] unlock_notify = ["libsqlite3-sys/unlock_notify"] -vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"] +vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"] csvtab = ["csv", "vtab"] # pointer passing interfaces: 3.20.0 array = ["vtab"] @@ -41,6 +41,7 @@ lru-cache = "0.1" chrono = { version = "0.4", optional = true } serde_json = { version = "1.0", optional = true } csv = { version = "1.0", optional = true } +lazy_static = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" @@ -62,7 +63,7 @@ name = "deny_single_threaded_sqlite_config" name = "vtab" [package.metadata.docs.rs] -features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace" ] +features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "vtab" ] all-features = false no-default-features = true default-target = "x86_64-unknown-linux-gnu" diff --git a/src/error.rs b/src/error.rs index d785c2e..d367431 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 4a2b18f..c85aa15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ extern crate libsqlite3_sys as ffi; extern crate lru_cache; #[macro_use] extern crate bitflags; -#[cfg(all(test, feature = "trace"))] +#[cfg(any(all(test, feature = "trace"), feature = "vtab"))] #[macro_use] extern crate lazy_static; @@ -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_handle; +use error::{error_from_sqlite_code, 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, error_from_sqlite_code}; +pub use error::Error; pub use ffi::ErrorCode; pub use cache::CachedStatement; diff --git a/src/vtab/array.rs b/src/vtab/array.rs index 36a8b08..4a145f8 100644 --- a/src/vtab/array.rs +++ b/src/vtab/array.rs @@ -4,11 +4,10 @@ use std::default::Default; use std::os::raw::{c_char, c_int, c_void}; use std::rc::Rc; -use error::error_from_sqlite_code; use ffi; use types::{ToSql, ToSqlOutput, Value}; -use vtab::{self, Context, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values}; -use {Connection, Error, Result}; +use vtab::{eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values}; +use {Connection, Result}; // http://sqlite.org/bindptr.html @@ -29,50 +28,11 @@ impl ToSql for Array { /// Register the "rarray" module. pub fn load_module(conn: &Connection) -> Result<()> { let aux: Option<()> = None; - conn.create_module("rarray", ArrayModule(&ARRAY_MODULE), aux) + conn.create_module::("rarray", &ARRAY_MODULE, aux) } -eponymous_module!( - ARRAY_MODULE, - ArrayModule, - ArrayTab, - (), - ArrayTabCursor, - None, - array_connect, - array_best_index, - array_disconnect, - None, - array_open, - array_close, - array_filter, - array_next, - array_eof, - array_column, - array_rowid -); - -#[repr(C)] -struct ArrayModule(&'static ffi::sqlite3_module); - -impl Module for ArrayModule { - type Aux = (); - type Table = ArrayTab; - - fn as_ptr(&self) -> *const ffi::sqlite3_module { - self.0 - } - - fn connect( - _: &mut VTabConnection, - _aux: Option<&()>, - _args: &[&[u8]], - ) -> Result<(String, ArrayTab)> { - let vtab = ArrayTab { - base: ffi::sqlite3_vtab::default(), - }; - Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab)) - } +lazy_static! { + static ref ARRAY_MODULE: Module = eponymous_only_module::(); } // Column numbers @@ -87,8 +47,20 @@ struct ArrayTab { } impl VTab for ArrayTab { + type Aux = (); type Cursor = ArrayTabCursor; + fn connect( + _: &mut VTabConnection, + _aux: Option<&()>, + _args: &[&[u8]], + ) -> Result<(String, ArrayTab)> { + let vtab = ArrayTab { + base: ffi::sqlite3_vtab::default(), + }; + Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), vtab)) + } + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { // Index of the pointer= constraint let mut ptr_idx = None; @@ -96,7 +68,7 @@ impl VTab for ArrayTab { if !constraint.is_usable() { continue; } - if constraint.operator() != vtab::IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { + if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { continue; } if let CARRAY_COLUMN_POINTER = constraint.column() { diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index 99b46ce..d17bd84 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -2,16 +2,15 @@ //! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension. extern crate csv; use std::fs::File; -use std::os::raw::{c_char, c_int, c_void}; +use std::os::raw::c_int; use std::path::Path; use std::result; use std::str; -use error::error_from_sqlite_code; use ffi; use types::Null; use vtab::{ - dequote, escape_double_quote, parse_boolean, Context, IndexInfo, Module, VTab, VTabConnection, + dequote, escape_double_quote, parse_boolean, simple_module, Context, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values, }; use {Connection, Error, Result}; @@ -29,32 +28,36 @@ use {Connection, Error, Result}; /// ``` pub fn load_module(conn: &Connection) -> Result<()> { let aux: Option<()> = None; - conn.create_module("csv", CSVModule(&CSV_MODULE), aux) + conn.create_module::("csv", &CSV_MODULE, aux) } -init_module!( - CSV_MODULE, - CSVModule, - CSVTab, - (), - CSVTabCursor, - csv_create, - csv_connect, - csv_best_index, - csv_disconnect, - csv_disconnect, - csv_open, - csv_close, - csv_filter, - csv_next, - csv_eof, - csv_column, - csv_rowid -); +lazy_static! { + static ref CSV_MODULE: Module = simple_module::(); +} -struct CSVModule(&'static ffi::sqlite3_module); +/// An instance of the CSV virtual table +#[repr(C)] +struct CSVTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, + /// Name of the CSV file + filename: String, + has_headers: bool, + delimiter: u8, + quote: u8, + /// Offset to start of data + offset_first_row: csv::Position, +} + +impl CSVTab { + fn reader(&self) -> result::Result, csv::Error> { + csv::ReaderBuilder::new() + .has_headers(self.has_headers) + .delimiter(self.delimiter) + .quote(self.quote) + .from_path(&self.filename) + } -impl CSVModule { fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> { let arg = try!(str::from_utf8(c_slice)).trim(); let mut split = arg.split('='); @@ -77,13 +80,9 @@ impl CSVModule { } } -impl Module for CSVModule { +impl VTab for CSVTab { type Aux = (); - type Table = CSVTab; - - fn as_ptr(&self) -> *const ffi::sqlite3_module { - self.0 - } + type Cursor = CSVTabCursor; fn connect( _: &mut VTabConnection, @@ -107,7 +106,7 @@ impl Module for CSVModule { let args = &args[3..]; for c_slice in args { - let (param, value) = try!(CSVModule::parameter(c_slice)); + let (param, value) = try!(CSVTab::parameter(c_slice)); match param { "filename" => { if !Path::new(value).exists() { @@ -151,7 +150,7 @@ impl Module for CSVModule { } } "delimiter" => { - if let Some(b) = CSVModule::parse_byte(value) { + if let Some(b) = CSVTab::parse_byte(value) { vtab.delimiter = b; } else { return Err(Error::ModuleError(format!( @@ -161,7 +160,7 @@ impl Module for CSVModule { } } "quote" => { - if let Some(b) = CSVModule::parse_byte(value) { + if let Some(b) = CSVTab::parse_byte(value) { if b == b'0' { vtab.quote = 0; } else { @@ -237,34 +236,6 @@ impl Module for CSVModule { Ok((schema.unwrap().to_owned(), vtab)) } -} - -/// An instance of the CSV virtual table -#[repr(C)] -struct CSVTab { - /// Base class. Must be first - base: ffi::sqlite3_vtab, - /// Name of the CSV file - filename: String, - has_headers: bool, - delimiter: u8, - quote: u8, - /// Offset to start of data - offset_first_row: csv::Position, -} - -impl CSVTab { - fn reader(&self) -> result::Result, csv::Error> { - csv::ReaderBuilder::new() - .has_headers(self.has_headers) - .delimiter(self.delimiter) - .quote(self.quote) - .from_path(&self.filename) - } -} - -impl VTab for CSVTab { - type Cursor = CSVTabCursor; // Only a forward full table scan is supported. fn best_index(&self, info: &mut IndexInfo) -> Result<()> { diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index 6a4f5d2..450ad41 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -2,11 +2,14 @@ //! (See http://sqlite.org/vtab.html) use std::borrow::Cow::{self, Borrowed, Owned}; use std::ffi::CString; +use std::marker::PhantomData; +use std::marker::Sync; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; use std::slice; use context::set_result; +use error::error_from_sqlite_code; use ffi; use types::{FromSql, FromSqlError, ToSql, ValueRef}; use {str_to_cstring, Connection, Error, InnerConnection, Result}; @@ -39,13 +42,91 @@ use {str_to_cstring, Connection, Error, InnerConnection, Result}; // \-> if not eof { cursor.column or xrowid } else { cursor.xclose } // -// db: *mut ffi::sqlite3 +// db: *mut ffi::sqlite3 => VTabConnection // module: *const ffi::sqlite3_module => Module // aux: *mut c_void => Module::Aux // ffi::sqlite3_vtab => VTab // ffi::sqlite3_vtab_cursor => VTabCursor -pub struct VTabConnection(pub *mut ffi::sqlite3); +#[repr(C)] +pub struct Module { + base: ffi::sqlite3_module, + phantom: PhantomData, +} + +unsafe impl Sync for Module {} + +/// Create a read-only virtual table implementation. +pub fn simple_module() -> Module { + // The xConnect and xCreate methods do the same thing, but they must be + // different so that the virtual table is not an eponymous virtual table. + let ffi_module = ffi::sqlite3_module { + iVersion: 1, + xCreate: Some(rust_create::), + xConnect: Some(rust_connect::), + xBestIndex: Some(rust_best_index::), + xDisconnect: Some(rust_disconnect::), + xDestroy: Some(rust_disconnect::), // TODO Validate: no rust_destroy + xOpen: Some(rust_open::), + xClose: Some(rust_close::), + xFilter: Some(rust_filter::), + xNext: Some(rust_next::), + xEof: Some(rust_eof::), + xColumn: Some(rust_column::), + xRowid: Some(rust_rowid::), + xUpdate: None, + xBegin: None, + xSync: None, + xCommit: None, + xRollback: None, + xFindFunction: None, + xRename: None, + xSavepoint: None, + xRelease: None, + xRollbackTo: None, + }; + Module { + base: ffi_module, + phantom: PhantomData::, + } +} + +/// Create an eponymous only virtual table implementation. +pub fn eponymous_only_module() -> Module { + // A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method + // For eponymous-only virtual tables, the xCreate method is NULL + let ffi_module = ffi::sqlite3_module { + iVersion: 1, + xCreate: None, + xConnect: Some(rust_connect::), + xBestIndex: Some(rust_best_index::), + xDisconnect: Some(rust_disconnect::), + xDestroy: None, + xOpen: Some(rust_open::), + xClose: Some(rust_close::), + xFilter: Some(rust_filter::), + xNext: Some(rust_next::), + xEof: Some(rust_eof::), + xColumn: Some(rust_column::), + xRowid: Some(rust_rowid::), + xUpdate: None, + xBegin: None, + xSync: None, + xCommit: None, + xRollback: None, + xFindFunction: None, + xRename: None, + xSavepoint: None, + xRelease: None, + xRollbackTo: None, + }; + Module { + base: ffi_module, + phantom: PhantomData::, + } +} + +pub struct VTabConnection(*mut ffi::sqlite3); impl VTabConnection { /// Get access to the underlying SQLite database connection handle. @@ -61,12 +142,10 @@ impl VTabConnection { } } -/// Module instance trait -pub trait Module { +/// Virtual table instance trait. +pub trait VTab: Sized { type Aux; - type Table: VTab; - - fn as_ptr(&self) -> *const ffi::sqlite3_module; + type Cursor: VTabCursor; /// Create a new instance of a virtual table in response to a CREATE VIRTUAL TABLE statement. /// The `db` parameter is a pointer to the SQLite database connection that is executing @@ -75,22 +154,19 @@ pub trait Module { db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], - ) -> Result<(String, Self::Table)> { + ) -> Result<(String, Self)> { Self::connect(db, aux, args) } + + // TODO Validate: no destroy + /// Similar to `create`. The difference is that `connect` is called to establish a new connection /// to an _existing_ virtual table whereas `create` is called to create a new virtual table from scratch. fn connect( db: &mut VTabConnection, aux: Option<&Self::Aux>, args: &[&[u8]], - ) -> Result<(String, Self::Table)>; -} - -/// Virtual table instance trait. -pub trait VTab: Sized { - type Cursor: VTabCursor; - + ) -> Result<(String, Self)>; /// Determine the best way to access the virtual table. fn best_index(&self, info: &mut IndexInfo) -> Result<()>; /// Create a new cursor used for accessing a virtual table. @@ -110,7 +186,7 @@ bitflags! { } } -pub struct IndexInfo(pub *mut ffi::sqlite3_index_info); +pub struct IndexInfo(*mut ffi::sqlite3_index_info); impl IndexInfo { pub fn constraints(&self) -> IndexConstraintIter { @@ -265,7 +341,7 @@ pub trait VTabCursor: Sized { fn rowid(&self) -> Result; } -pub struct Context(pub *mut ffi::sqlite3_context); +pub struct Context(*mut ffi::sqlite3_context); impl Context { pub fn set_result(&mut self, value: &T) -> Result<()> { @@ -276,7 +352,7 @@ impl Context { } pub struct Values<'a> { - pub args: &'a [*mut ffi::sqlite3_value], + args: &'a [*mut ffi::sqlite3_value], } impl<'a> Values<'a> { @@ -352,34 +428,34 @@ impl<'a> Iterator for ValueIter<'a> { impl Connection { /// Register a virtual table implementation. - pub fn create_module( + pub fn create_module( &self, module_name: &str, - module: M, - aux: Option, + module: &Module, + aux: Option, ) -> Result<()> { - self.db.borrow_mut().create_module(module_name, module, aux) + self.db.borrow_mut().create_module::(module_name, module, aux) } } impl InnerConnection { - fn create_module( + fn create_module( &mut self, module_name: &str, - module: M, - aux: Option, + module: &Module, + aux: Option, ) -> Result<()> { let c_name = try!(str_to_cstring(module_name)); let r = match aux { Some(aux) => { - let boxed_aux: *mut M::Aux = Box::into_raw(Box::new(aux)); + let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux)); unsafe { ffi::sqlite3_create_module_v2( self.db(), c_name.as_ptr(), - module.as_ptr(), + &module.base, boxed_aux as *mut c_void, - Some(free_boxed_value::), + Some(free_boxed_value::), ) } } @@ -387,7 +463,7 @@ impl InnerConnection { ffi::sqlite3_create_module_v2( self.db(), c_name.as_ptr(), - module.as_ptr(), + &module.base, ptr::null_mut(), None, ) @@ -447,339 +523,237 @@ unsafe extern "C" fn free_boxed_value(p: *mut c_void) { let _: Box = Box::from_raw(p as *mut T); } -#[macro_export] -macro_rules! init_module { - ( - $module_name:ident, - $module:ident, - $vtab:ident, - $aux:ty, - $cursor:ty, - $create:ident, - $connect:ident, - $best_index:ident, - $disconnect:ident, - $destroy:ident, - $open:ident, - $close:ident, - $filter:ident, - $next:ident, - $eof:ident, - $column:ident, - $rowid:ident - ) => { - static $module_name: ffi::sqlite3_module = ffi::sqlite3_module { - iVersion: 1, - xCreate: Some($create), - xConnect: Some($connect), - xBestIndex: Some($best_index), - xDisconnect: Some($disconnect), - xDestroy: Some($destroy), - xOpen: Some($open), - xClose: Some($close), - xFilter: Some($filter), - xNext: Some($next), - xEof: Some($eof), - xColumn: Some($column), - xRowid: Some($rowid), - xUpdate: None, // TODO - xBegin: None, - xSync: None, - xCommit: None, - xRollback: None, - xFindFunction: None, - xRename: None, - xSavepoint: None, - xRelease: None, - xRollbackTo: None, - }; +unsafe extern "C" fn rust_create( + db: *mut ffi::sqlite3, + aux: *mut c_void, + argc: c_int, + argv: *const *const c_char, + pp_vtab: *mut *mut ffi::sqlite3_vtab, + err_msg: *mut *mut c_char, +) -> c_int + where T: VTab { + use std::error::Error as StdError; + use std::ffi::CStr; + use std::slice; - // The xConnect and xCreate methods do the same thing, but they must be - // different so that the virtual table is not an eponymous virtual table. - create_or_connect!($module, $vtab, $aux, $create, create); - common_decl!( - $module, - $vtab, - $aux, - $cursor, - $connect, - $best_index, - $disconnect, - $destroy, - $open, - $close, - $filter, - $next, - $eof, - $column, - $rowid - ); - }; -} // init_module macro end - -#[macro_export] -macro_rules! eponymous_module { - ( - $module_name:ident, - $module:ident, - $vtab:ident, - $aux:ty, - $cursor:ty, - $create:expr, - $connect:ident, - $best_index:ident, - $disconnect:ident, - $destroy:expr, - $open:ident, - $close:ident, - $filter:ident, - $next:ident, - $eof:ident, - $column:ident, - $rowid:ident - ) => { - static $module_name: ffi::sqlite3_module = ffi::sqlite3_module { - iVersion: 1, - xCreate: $create, /* For eponymous-only virtual tables, the xCreate method is NULL */ - xConnect: Some($connect), /* A virtual table is eponymous if its xCreate method is - the exact same function as the xConnect method */ - xBestIndex: Some($best_index), - xDisconnect: Some($disconnect), - xDestroy: $destroy, - xOpen: Some($open), - xClose: Some($close), - xFilter: Some($filter), - xNext: Some($next), - xEof: Some($eof), - xColumn: Some($column), - xRowid: Some($rowid), - xUpdate: None, // TODO - xBegin: None, - xSync: None, - xCommit: None, - xRollback: None, - xFindFunction: None, - xRename: None, - xSavepoint: None, - xRelease: None, - xRollbackTo: None, - }; - - common_decl!( - $module, - $vtab, - $aux, - $cursor, - $connect, - $best_index, - $disconnect, - $destroy, - $open, - $close, - $filter, - $next, - $eof, - $column, - $rowid - ); - }; -} // 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( - db: *mut ffi::sqlite3, - aux: *mut c_void, - argc: c_int, - argv: *const *const c_char, - pp_vtab: *mut *mut ffi::sqlite3_vtab, - err_msg: *mut *mut c_char, - ) -> c_int { - use std::error::Error as StdError; - use std::ffi::CStr; - use std::slice; - use $crate::vtab::mprintf; - - let mut conn = VTabConnection(db); - let aux = aux as *mut $aux; - let args = slice::from_raw_parts(argv, argc as usize); - let vec = args - .iter() - .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error> - .collect::>(); - match $module::$module_func(&mut conn, aux.as_ref(), &vec[..]) { - Ok((sql, vtab)) => { - match ::std::ffi::CString::new(sql) { - Ok(c_sql) => { - let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); - if rc == ffi::SQLITE_OK { - let boxed_vtab: *mut $vtab = Box::into_raw(Box::new(vtab)); - *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; - ffi::SQLITE_OK - } else { - let err = error_from_sqlite_code(rc, None); - *err_msg = mprintf(err.description()); - rc - } - } - Err(err) => { - *err_msg = mprintf(err.description()); - ffi::SQLITE_ERROR - } + let mut conn = VTabConnection(db); + let aux = aux as *mut T::Aux; + let args = slice::from_raw_parts(argv, argc as usize); + let vec = args + .iter() + .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error> + .collect::>(); + match T::create(&mut conn, aux.as_ref(), &vec[..]) { + Ok((sql, vtab)) => { + match ::std::ffi::CString::new(sql) { + Ok(c_sql) => { + let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); + if rc == ffi::SQLITE_OK { + let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab)); + *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; + ffi::SQLITE_OK + } else { + let err = error_from_sqlite_code(rc, None); + *err_msg = mprintf(err.description()); + rc } } - Err(Error::SqliteFailure(err, s)) => { - if let Some(s) = s { - *err_msg = mprintf(&s); - } - err.extended_code - } Err(err) => { *err_msg = mprintf(err.description()); ffi::SQLITE_ERROR } } } - }; -} // create_or_connect macro end + Err(Error::SqliteFailure(err, s)) => { + if let Some(s) = s { + *err_msg = mprintf(&s); + } + err.extended_code + } + Err(err) => { + *err_msg = mprintf(err.description()); + ffi::SQLITE_ERROR + } + } +} -#[macro_export] -macro_rules! common_decl { - ( - $module:ident, - $vtab:ident, - $aux:ty, - $cursor:ty, - $connect:ident, - $best_index:ident, - $disconnect:ident, - $destroy:expr, - $open:ident, - $close:ident, - $filter:ident, - $next:ident, - $eof:ident, - $column:ident, - $rowid:ident - ) => { - create_or_connect!($module, $vtab, $aux, $connect, connect); - unsafe extern "C" fn $best_index( - vtab: *mut ffi::sqlite3_vtab, - info: *mut ffi::sqlite3_index_info, - ) -> c_int { - use std::error::Error as StdError; - 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) { - Ok(_) => ffi::SQLITE_OK, - Err(Error::SqliteFailure(err, s)) => { - if let Some(err_msg) = s { - set_err_msg(vtab, &err_msg); +unsafe extern "C" fn rust_connect( + db: *mut ffi::sqlite3, + aux: *mut c_void, + argc: c_int, + argv: *const *const c_char, + pp_vtab: *mut *mut ffi::sqlite3_vtab, + err_msg: *mut *mut c_char, +) -> c_int + where T: VTab { + use std::error::Error as StdError; + use std::ffi::CStr; + use std::slice; + + let mut conn = VTabConnection(db); + let aux = aux as *mut T::Aux; + let args = slice::from_raw_parts(argv, argc as usize); + let vec = args + .iter() + .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error> + .collect::>(); + match T::connect(&mut conn, aux.as_ref(), &vec[..]) { + Ok((sql, vtab)) => { + match ::std::ffi::CString::new(sql) { + Ok(c_sql) => { + let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); + if rc == ffi::SQLITE_OK { + let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab)); + *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; + ffi::SQLITE_OK + } else { + let err = error_from_sqlite_code(rc, None); + *err_msg = mprintf(err.description()); + rc } - err.extended_code } Err(err) => { - set_err_msg(vtab, err.description()); + *err_msg = mprintf(err.description()); ffi::SQLITE_ERROR } } } - unsafe extern "C" fn $disconnect(vtab: *mut ffi::sqlite3_vtab) -> c_int { - let vtab = vtab as *mut $vtab; - let _: Box<$vtab> = Box::from_raw(vtab); + Err(Error::SqliteFailure(err, s)) => { + if let Some(s) = s { + *err_msg = mprintf(&s); + } + err.extended_code + } + Err(err) => { + *err_msg = mprintf(err.description()); + ffi::SQLITE_ERROR + } + } +} + +unsafe extern "C" fn rust_best_index( + vtab: *mut ffi::sqlite3_vtab, + info: *mut ffi::sqlite3_index_info, +) -> c_int + where T: VTab { + use std::error::Error as StdError; + let vt = vtab as *mut T; + let mut idx_info = IndexInfo(info); + match (*vt).best_index(&mut idx_info) { + Ok(_) => ffi::SQLITE_OK, + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); + } + err.extended_code + } + Err(err) => { + set_err_msg(vtab, err.description()); + ffi::SQLITE_ERROR + } + } +} + +unsafe extern "C" fn rust_disconnect(vtab: *mut ffi::sqlite3_vtab) -> c_int + where T: VTab { + let vtab = vtab as *mut T; + let _: Box = Box::from_raw(vtab); + ffi::SQLITE_OK +} + +unsafe extern "C" fn rust_open( + vtab: *mut ffi::sqlite3_vtab, + pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, +) -> c_int + where T: VTab { + use std::error::Error as StdError; + let vt = vtab as *mut T; + match (*vt).open() { + Ok(cursor) => { + let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor)); + *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor; ffi::SQLITE_OK } - - unsafe extern "C" fn $open( - vtab: *mut ffi::sqlite3_vtab, - pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, - ) -> c_int { - use std::error::Error as StdError; - use $crate::vtab::set_err_msg; - let vt = vtab as *mut $vtab; - match (*vt).open() { - Ok(cursor) => { - let boxed_cursor: *mut $cursor = Box::into_raw(Box::new(cursor)); - *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor; - ffi::SQLITE_OK - } - Err(Error::SqliteFailure(err, s)) => { - if let Some(err_msg) = s { - set_err_msg(vtab, &err_msg); - } - err.extended_code - } - Err(err) => { - set_err_msg(vtab, err.description()); - ffi::SQLITE_ERROR - } + Err(Error::SqliteFailure(err, s)) => { + if let Some(err_msg) = s { + set_err_msg(vtab, &err_msg); } + err.extended_code } - unsafe extern "C" fn $close(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int { - let cr = cursor as *mut $cursor; - let _: Box<$cursor> = Box::from_raw(cr); - ffi::SQLITE_OK + Err(err) => { + set_err_msg(vtab, err.description()); + ffi::SQLITE_ERROR } + } +} - unsafe extern "C" fn $filter( - cursor: *mut ffi::sqlite3_vtab_cursor, - idx_num: c_int, - idx_str: *const c_char, - argc: c_int, - argv: *mut *mut ffi::sqlite3_value, - ) -> c_int { - use std::ffi::CStr; - use std::slice; - use std::str; - use $crate::vtab::{cursor_error, Values}; - let idx_name = if idx_str.is_null() { - None - } else { - let c_slice = CStr::from_ptr(idx_str).to_bytes(); - Some(str::from_utf8_unchecked(c_slice)) - }; - let args = slice::from_raw_parts_mut(argv, argc as usize); - let values = Values { args: args }; - let cr = cursor as *mut $cursor; - cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values)) - } - unsafe extern "C" fn $next(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int { - use $crate::vtab::cursor_error; - let cr = cursor as *mut $cursor; - cursor_error(cursor, (*cr).next()) - } - unsafe extern "C" fn $eof(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int { - let cr = cursor as *mut $cursor; - (*cr).eof() as c_int - } - unsafe extern "C" fn $column( - cursor: *mut ffi::sqlite3_vtab_cursor, - ctx: *mut ffi::sqlite3_context, - i: c_int, - ) -> c_int { - 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)) - } - unsafe extern "C" fn $rowid( - cursor: *mut ffi::sqlite3_vtab_cursor, - p_rowid: *mut ffi::sqlite3_int64, - ) -> c_int { - use $crate::vtab::cursor_error; - let cr = cursor as *mut $cursor; - match (*cr).rowid() { - Ok(rowid) => { - *p_rowid = rowid; - ffi::SQLITE_OK - } - err => cursor_error(cursor, err), - } - } +unsafe extern "C" fn rust_close(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int + where C: VTabCursor { + let cr = cursor as *mut C; + let _: Box = Box::from_raw(cr); + ffi::SQLITE_OK +} + +unsafe extern "C" fn rust_filter( + cursor: *mut ffi::sqlite3_vtab_cursor, + idx_num: c_int, + idx_str: *const c_char, + argc: c_int, + argv: *mut *mut ffi::sqlite3_value, +) -> c_int + where C: VTabCursor { + use std::ffi::CStr; + use std::slice; + use std::str; + let idx_name = if idx_str.is_null() { + None + } else { + let c_slice = CStr::from_ptr(idx_str).to_bytes(); + Some(str::from_utf8_unchecked(c_slice)) }; -} // common_decl macro end + let args = slice::from_raw_parts_mut(argv, argc as usize); + let values = Values { args: args }; + let cr = cursor as *mut C; + cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values)) +} + +unsafe extern "C" fn rust_next(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int + where C: VTabCursor { + let cr = cursor as *mut C; + cursor_error(cursor, (*cr).next()) +} + +unsafe extern "C" fn rust_eof(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int + where C: VTabCursor { + let cr = cursor as *mut C; + (*cr).eof() as c_int +} + +unsafe extern "C" fn rust_column( + cursor: *mut ffi::sqlite3_vtab_cursor, + ctx: *mut ffi::sqlite3_context, + i: c_int, +) -> c_int + where C: VTabCursor { + let cr = cursor as *mut C; + let mut ctxt = Context(ctx); + result_error(ctx, (*cr).column(&mut ctxt, i)) +} + +unsafe extern "C" fn rust_rowid( + cursor: *mut ffi::sqlite3_vtab_cursor, + p_rowid: *mut ffi::sqlite3_int64, +) -> c_int + where C: VTabCursor { + let cr = cursor as *mut C; + match (*cr).rowid() { + Ok(rowid) => { + *p_rowid = rowid; + ffi::SQLITE_OK + } + err => cursor_error(cursor, err), + } +} /// Virtual table cursors can set an error message by assigning a string to `zErrMsg`. pub unsafe fn cursor_error(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result) -> c_int { @@ -800,7 +774,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`. -pub unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { +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); } @@ -809,7 +783,7 @@ pub 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. -pub unsafe fn result_error(ctx: *mut ffi::sqlite3_context, result: Result) -> c_int { +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/src/vtab/series.rs b/src/vtab/series.rs index 7846325..cefadc3 100644 --- a/src/vtab/series.rs +++ b/src/vtab/series.rs @@ -1,64 +1,21 @@ //! generate series virtual table. //! Port of C [generate series "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c). use std::default::Default; -use std::os::raw::{c_char, c_int, c_void}; +use std::os::raw::c_int; -use error::error_from_sqlite_code; use ffi; use types::Type; -use vtab::{self, Context, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values}; -use {Connection, Error, Result}; +use vtab::{eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection, VTabCursor, Values}; +use {Connection, Result}; /// Register the "generate_series" module. pub fn load_module(conn: &Connection) -> Result<()> { let aux: Option<()> = None; - conn.create_module("generate_series", Series(&SERIES_MODULE), aux) + conn.create_module::("generate_series", &SERIES_MODULE, aux) } -eponymous_module!( - SERIES_MODULE, - Series, - SeriesTab, - (), - SeriesTabCursor, - None, - series_connect, - series_best_index, - series_disconnect, - None, - series_open, - series_close, - series_filter, - series_next, - series_eof, - series_column, - series_rowid -); - -#[repr(C)] -struct Series(&'static ffi::sqlite3_module); - -impl Module for Series { - type Aux = (); - type Table = SeriesTab; - - fn as_ptr(&self) -> *const ffi::sqlite3_module { - self.0 - } - - fn connect( - _: &mut VTabConnection, - _aux: Option<&()>, - _args: &[&[u8]], - ) -> Result<(String, SeriesTab)> { - let vtab = SeriesTab { - base: ffi::sqlite3_vtab::default(), - }; - Ok(( - "CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(), - vtab, - )) - } +lazy_static! { + static ref SERIES_MODULE: Module = eponymous_only_module::(); } // Column numbers @@ -91,8 +48,23 @@ struct SeriesTab { } impl VTab for SeriesTab { + type Aux = (); type Cursor = SeriesTabCursor; + fn connect( + _: &mut VTabConnection, + _aux: Option<&()>, + _args: &[&[u8]], + ) -> Result<(String, SeriesTab)> { + let vtab = SeriesTab { + base: ffi::sqlite3_vtab::default(), + }; + Ok(( + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(), + vtab, + )) + } + fn best_index(&self, info: &mut IndexInfo) -> Result<()> { // The query plan bitmask let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty(); @@ -106,7 +78,7 @@ impl VTab for SeriesTab { if !constraint.is_usable() { continue; } - if constraint.operator() != vtab::IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { + if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ { continue; } match constraint.column() { diff --git a/tests/vtab.rs b/tests/vtab.rs index eeb4599..c2195de 100644 --- a/tests/vtab.rs +++ b/tests/vtab.rs @@ -1,7 +1,6 @@ //! Ensure Virtual tables can be declared outside `rusqlite` crate. #[cfg(feature = "vtab")] -#[macro_use] extern crate rusqlite; extern crate libsqlite3_sys as ffi; @@ -9,40 +8,21 @@ extern crate libsqlite3_sys as ffi; #[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}; + use rusqlite::vtab::{eponymous_only_module, Context, IndexInfo, VTab, VTabConnection, VTabCursor, Values}; + use rusqlite::{Connection, Result}; + use std::os::raw::c_int; - 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 - ); + let module = eponymous_only_module::(); #[repr(C)] - struct DummyModule(&'static ffi::sqlite3_module); + struct DummyTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, + } - impl Module for DummyModule { + impl VTab for DummyTab { type Aux = (); - type Table = DummyTab; - - fn as_ptr(&self) -> *const ffi::sqlite3_module { - self.0 - } + type Cursor = DummyTabCursor; fn connect( _: &mut VTabConnection, @@ -54,16 +34,6 @@ fn test_dummy_module() { }; 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.); @@ -116,9 +86,7 @@ fn test_dummy_module() { let db = Connection::open_in_memory().unwrap(); - let module = DummyModule(&DUMMY_MODULE); - - db.create_module("dummy", module, None).unwrap(); + db.create_module::("dummy", &module, None).unwrap(); let version = unsafe { ffi::sqlite3_libversion_number() }; if version < 3008012 {