Introduce Module trait to make create_module type safe

This commit is contained in:
gwenn 2018-06-20 20:01:38 +02:00
parent 2a684384fd
commit afeb5d4d47
4 changed files with 141 additions and 75 deletions

View File

@ -6,7 +6,7 @@ use std::rc::Rc;
use ffi; use ffi;
use types::{ToSql, ToSqlOutput, Value}; use types::{ToSql, ToSqlOutput, Value};
use vtab::{self, declare_vtab, Context, IndexInfo, VTab, VTabCursor, Values}; use vtab::{self, declare_vtab, Context, IndexInfo, Module, VTab, VTabCursor, Values};
use {Connection, Error, Result}; use {Connection, Error, Result};
// http://sqlite.org/bindptr.html // http://sqlite.org/bindptr.html
@ -28,11 +28,12 @@ impl ToSql for Array {
/// Register the "rarray" module. /// Register the "rarray" module.
pub fn load_module(conn: &Connection) -> Result<()> { pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None; let aux: Option<()> = None;
conn.create_module("rarray", &ARRAY_MODULE, aux) conn.create_module("rarray", ArrayModule(&ARRAY_MODULE), aux)
} }
eponymous_module!( eponymous_module!(
ARRAY_MODULE, ARRAY_MODULE,
ArrayModule,
ArrayTab, ArrayTab,
(), (),
ArrayTabCursor, ArrayTabCursor,
@ -50,6 +51,30 @@ eponymous_module!(
array_rowid 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
}
unsafe fn connect(
db: *mut ffi::sqlite3,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<ArrayTab> {
let vtab = ArrayTab {
base: Default::default(),
};
try!(declare_vtab(db, "CREATE TABLE x(value,pointer hidden)"));
Ok(vtab)
}
}
// Column numbers // Column numbers
// const CARRAY_COLUMN_VALUE : c_int = 0; // const CARRAY_COLUMN_VALUE : c_int = 0;
const CARRAY_COLUMN_POINTER: c_int = 1; const CARRAY_COLUMN_POINTER: c_int = 1;
@ -62,17 +87,8 @@ struct ArrayTab {
} }
impl VTab for ArrayTab { impl VTab for ArrayTab {
type Aux = ();
type Cursor = ArrayTabCursor; type Cursor = ArrayTabCursor;
unsafe fn connect(db: *mut ffi::sqlite3, _aux: *mut (), _args: &[&[u8]]) -> Result<ArrayTab> {
let vtab = ArrayTab {
base: Default::default(),
};
try!(declare_vtab(db, "CREATE TABLE x(value,pointer hidden)"));
Ok(vtab)
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> { fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// Index of the pointer= constraint // Index of the pointer= constraint
let mut ptr_idx = None; let mut ptr_idx = None;

View File

@ -10,7 +10,7 @@ use std::str;
use ffi; use ffi;
use types::Null; use types::Null;
use vtab::{ use vtab::{
declare_vtab, dequote, escape_double_quote, parse_boolean, Context, IndexInfo, VTab, declare_vtab, dequote, escape_double_quote, parse_boolean, Context, IndexInfo, Module, VTab,
VTabCursor, Values, VTabCursor, Values,
}; };
use {Connection, Error, Result}; use {Connection, Error, Result};
@ -28,11 +28,12 @@ use {Connection, Error, Result};
/// ``` /// ```
pub fn load_module(conn: &Connection) -> Result<()> { pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None; let aux: Option<()> = None;
conn.create_module("csv", &CSV_MODULE, aux) conn.create_module("csv", CSVModule(&CSV_MODULE), aux)
} }
init_module!( init_module!(
CSV_MODULE, CSV_MODULE,
CSVModule,
CSVTab, CSVTab,
(), (),
CSVTabCursor, CSVTabCursor,
@ -50,29 +51,9 @@ init_module!(
csv_rowid csv_rowid
); );
/// An instance of the CSV virtual table struct CSVModule(&'static ffi::sqlite3_module);
#[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::Reader<File>, 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)> { fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
let arg = try!(str::from_utf8(c_slice)).trim(); let arg = try!(str::from_utf8(c_slice)).trim();
let mut split = arg.split('='); let mut split = arg.split('=');
@ -95,11 +76,15 @@ impl CSVTab {
} }
} }
impl VTab for CSVTab { impl Module for CSVModule {
type Aux = (); type Aux = ();
type Cursor = CSVTabCursor; type Table = CSVTab;
unsafe fn connect(db: *mut ffi::sqlite3, _aux: *mut (), args: &[&[u8]]) -> Result<CSVTab> { fn as_ptr(&self) -> *const ffi::sqlite3_module {
self.0
}
unsafe fn connect(db: *mut ffi::sqlite3, _aux: Option<&()>, args: &[&[u8]]) -> Result<CSVTab> {
if args.len() < 4 { if args.len() < 4 {
return Err(Error::ModuleError("no CSV file specified".to_owned())); return Err(Error::ModuleError("no CSV file specified".to_owned()));
} }
@ -117,7 +102,7 @@ impl VTab for CSVTab {
let args = &args[3..]; let args = &args[3..];
for c_slice in args { for c_slice in args {
let (param, value) = try!(CSVTab::parameter(c_slice)); let (param, value) = try!(CSVModule::parameter(c_slice));
match param { match param {
"filename" => { "filename" => {
if !Path::new(value).exists() { if !Path::new(value).exists() {
@ -161,7 +146,7 @@ impl VTab for CSVTab {
} }
} }
"delimiter" => { "delimiter" => {
if let Some(b) = CSVTab::parse_byte(value) { if let Some(b) = CSVModule::parse_byte(value) {
vtab.delimiter = b; vtab.delimiter = b;
} else { } else {
return Err(Error::ModuleError(format!( return Err(Error::ModuleError(format!(
@ -171,7 +156,7 @@ impl VTab for CSVTab {
} }
} }
"quote" => { "quote" => {
if let Some(b) = CSVTab::parse_byte(value) { if let Some(b) = CSVModule::parse_byte(value) {
if b == b'0' { if b == b'0' {
vtab.quote = 0; vtab.quote = 0;
} else { } else {
@ -248,6 +233,34 @@ impl VTab for CSVTab {
try!(declare_vtab(db, &schema.unwrap())); try!(declare_vtab(db, &schema.unwrap()));
Ok(vtab) Ok(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::Reader<File>, 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. // Only a forward full table scan is supported.
fn best_index(&self, info: &mut IndexInfo) -> Result<()> { fn best_index(&self, info: &mut IndexInfo) -> Result<()> {

View File

@ -40,20 +40,36 @@ use {str_to_cstring, Connection, Error, InnerConnection, Result};
// \-> if not eof { cursor.column or xrowid } else { cursor.xclose } // \-> if not eof { cursor.column or xrowid } else { cursor.xclose }
// //
/// Virtual table instance trait. /// Module instance trait
pub trait VTab: Sized { pub trait Module {
type Aux; type Aux;
type Cursor: VTabCursor; type Table: VTab;
fn as_ptr(&self) -> *const ffi::sqlite3_module;
/// Create a new instance of a virtual table in response to a CREATE VIRTUAL TABLE statement. /// 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 /// The `db` parameter is a pointer to the SQLite database connection that is executing
/// the CREATE VIRTUAL TABLE statement. /// the CREATE VIRTUAL TABLE statement.
unsafe fn create(db: *mut ffi::sqlite3, aux: *mut Self::Aux, args: &[&[u8]]) -> Result<Self> { unsafe fn create(
db: *mut ffi::sqlite3,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<Self::Table> {
Self::connect(db, aux, args) Self::connect(db, aux, args)
} }
/// Similar to `create`. The difference is that `connect` is called to establish a new connection /// 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. /// to an _existing_ virtual table whereas `create` is called to create a new virtual table from scratch.
unsafe fn connect(db: *mut ffi::sqlite3, aux: *mut Self::Aux, args: &[&[u8]]) -> Result<Self>; unsafe fn connect(
db: *mut ffi::sqlite3,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<Self::Table>;
}
/// Virtual table instance trait.
pub trait VTab: Sized {
type Cursor: VTabCursor;
/// Determine the best way to access the virtual table. /// Determine the best way to access the virtual table.
fn best_index(&self, info: &mut IndexInfo) -> Result<()>; fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
/// Create a new cursor used for accessing a virtual table. /// Create a new cursor used for accessing a virtual table.
@ -291,34 +307,34 @@ impl<'a> Iterator for ValueIter<'a> {
impl Connection { impl Connection {
/// Register a virtual table implementation. /// Register a virtual table implementation.
pub fn create_module<A>( pub fn create_module<M: Module>(
&self, &self,
module_name: &str, module_name: &str,
module: *const ffi::sqlite3_module, module: M,
aux: Option<A>, aux: Option<M::Aux>,
) -> Result<()> { ) -> Result<()> {
self.db.borrow_mut().create_module(module_name, module, aux) self.db.borrow_mut().create_module(module_name, module, aux)
} }
} }
impl InnerConnection { impl InnerConnection {
fn create_module<A>( fn create_module<M: Module>(
&mut self, &mut self,
module_name: &str, module_name: &str,
module: *const ffi::sqlite3_module, module: M,
aux: Option<A>, aux: Option<M::Aux>,
) -> Result<()> { ) -> Result<()> {
let c_name = try!(str_to_cstring(module_name)); let c_name = try!(str_to_cstring(module_name));
let r = match aux { let r = match aux {
Some(aux) => { Some(aux) => {
let boxed_aux: *mut A = Box::into_raw(Box::new(aux)); let boxed_aux: *mut M::Aux = Box::into_raw(Box::new(aux));
unsafe { unsafe {
ffi::sqlite3_create_module_v2( ffi::sqlite3_create_module_v2(
self.db(), self.db(),
c_name.as_ptr(), c_name.as_ptr(),
module, module.as_ptr(),
boxed_aux as *mut c_void, boxed_aux as *mut c_void,
Some(free_boxed_value::<A>), Some(free_boxed_value::<M::Aux>),
) )
} }
} }
@ -326,7 +342,7 @@ impl InnerConnection {
ffi::sqlite3_create_module_v2( ffi::sqlite3_create_module_v2(
self.db(), self.db(),
c_name.as_ptr(), c_name.as_ptr(),
module, module.as_ptr(),
ptr::null_mut(), ptr::null_mut(),
None, None,
) )
@ -401,6 +417,7 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
macro_rules! init_module { macro_rules! init_module {
( (
$module_name:ident, $module_name:ident,
$module:ident,
$vtab:ident, $vtab:ident,
$aux:ty, $aux:ty,
$cursor:ty, $cursor:ty,
@ -445,8 +462,9 @@ macro_rules! init_module {
// The xConnect and xCreate methods do the same thing, but they must be // 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. // different so that the virtual table is not an eponymous virtual table.
create_or_connect!($vtab, $aux, $create, create); create_or_connect!($module, $vtab, $aux, $create, create);
common_decl!( common_decl!(
$module,
$vtab, $vtab,
$aux, $aux,
$cursor, $cursor,
@ -469,6 +487,7 @@ macro_rules! init_module {
macro_rules! eponymous_module { macro_rules! eponymous_module {
( (
$module_name:ident, $module_name:ident,
$module:ident,
$vtab:ident, $vtab:ident,
$aux:ty, $aux:ty,
$cursor:ty, $cursor:ty,
@ -513,6 +532,7 @@ macro_rules! eponymous_module {
}; };
common_decl!( common_decl!(
$module,
$vtab, $vtab,
$aux, $aux,
$cursor, $cursor,
@ -532,7 +552,7 @@ macro_rules! eponymous_module {
} // eponymous_module macro end } // eponymous_module macro end
macro_rules! create_or_connect { macro_rules! create_or_connect {
($vtab:ident, $aux:ty, $create_or_connect:ident, $vtab_func:ident) => { ($module:ident, $vtab:ident, $aux:ty, $create_or_connect:ident, $module_func:ident) => {
unsafe extern "C" fn $create_or_connect( unsafe extern "C" fn $create_or_connect(
db: *mut ffi::sqlite3, db: *mut ffi::sqlite3,
aux: *mut c_void, aux: *mut c_void,
@ -550,9 +570,9 @@ macro_rules! create_or_connect {
let args = slice::from_raw_parts(argv, argc as usize); let args = slice::from_raw_parts(argv, argc as usize);
let vec = args let vec = args
.iter() .iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) .map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match $vtab::$vtab_func(db, aux, &vec[..]) { match $module::$module_func(db, aux.as_ref(), &vec[..]) {
Ok(vtab) => { Ok(vtab) => {
let boxed_vtab: *mut $vtab = Box::into_raw(Box::new(vtab)); let boxed_vtab: *mut $vtab = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
@ -575,6 +595,7 @@ macro_rules! create_or_connect {
macro_rules! common_decl { macro_rules! common_decl {
( (
$module:ident,
$vtab:ident, $vtab:ident,
$aux:ty, $aux:ty,
$cursor:ty, $cursor:ty,
@ -590,7 +611,7 @@ macro_rules! common_decl {
$column:ident, $column:ident,
$rowid:ident $rowid:ident
) => { ) => {
create_or_connect!($vtab, $aux, $connect, connect); create_or_connect!($module, $vtab, $aux, $connect, connect);
unsafe extern "C" fn $best_index( unsafe extern "C" fn $best_index(
vtab: *mut ffi::sqlite3_vtab, vtab: *mut ffi::sqlite3_vtab,
info: *mut ffi::sqlite3_index_info, info: *mut ffi::sqlite3_index_info,

View File

@ -5,17 +5,18 @@ use std::os::raw::{c_char, c_int, c_void};
use ffi; use ffi;
use types::Type; use types::Type;
use vtab::{self, declare_vtab, Context, IndexInfo, VTab, VTabCursor, Values}; use vtab::{self, declare_vtab, Context, IndexInfo, Module, VTab, VTabCursor, Values};
use {Connection, Error, Result}; use {Connection, Error, Result};
/// Register the "generate_series" module. /// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> { pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None; let aux: Option<()> = None;
conn.create_module("generate_series", &SERIES_MODULE, aux) conn.create_module("generate_series", Series(&SERIES_MODULE), aux)
} }
eponymous_module!( eponymous_module!(
SERIES_MODULE, SERIES_MODULE,
Series,
SeriesTab, SeriesTab,
(), (),
SeriesTabCursor, SeriesTabCursor,
@ -33,6 +34,33 @@ eponymous_module!(
series_rowid 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
}
unsafe fn connect(
db: *mut ffi::sqlite3,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<SeriesTab> {
let vtab = SeriesTab {
base: Default::default(),
};
try!(declare_vtab(
db,
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)"
));
Ok(vtab)
}
}
// Column numbers // Column numbers
// const SERIES_COLUMN_VALUE : c_int = 0; // const SERIES_COLUMN_VALUE : c_int = 0;
const SERIES_COLUMN_START: c_int = 1; const SERIES_COLUMN_START: c_int = 1;
@ -63,20 +91,8 @@ struct SeriesTab {
} }
impl VTab for SeriesTab { impl VTab for SeriesTab {
type Aux = ();
type Cursor = SeriesTabCursor; type Cursor = SeriesTabCursor;
unsafe fn connect(db: *mut ffi::sqlite3, _aux: *mut (), _args: &[&[u8]]) -> Result<SeriesTab> {
let vtab = SeriesTab {
base: Default::default(),
};
try!(declare_vtab(
db,
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)"
));
Ok(vtab)
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> { fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// The query plan bitmask // The query plan bitmask
let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty(); let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty();