This commit is contained in:
gwenn 2018-05-06 17:21:36 +02:00
parent 8e6ea05efa
commit 2e2b5c41f4
4 changed files with 495 additions and 392 deletions

View File

@ -7,10 +7,13 @@ use std::path::Path;
use std::result; use std::result;
use std::str; use std::str;
use {Connection, Error, Result};
use ffi; use ffi;
use types::Null; use types::Null;
use vtab::{declare_vtab, dequote, escape_double_quote, parse_boolean, Context, IndexInfo, Values, VTab, VTabCursor}; use vtab::{
declare_vtab, dequote, escape_double_quote, parse_boolean, Context, IndexInfo, VTab,
VTabCursor, Values,
};
use {Connection, Error, Result};
/// Register the "csv" module. /// Register the "csv" module.
/// ```sql /// ```sql
@ -28,7 +31,8 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("csv", &CSV_MODULE, aux) conn.create_module("csv", &CSV_MODULE, aux)
} }
init_module!(CSV_MODULE, init_module!(
CSV_MODULE,
CSVTab, CSVTab,
CSVTabCursor, CSVTabCursor,
csv_create, csv_create,
@ -42,7 +46,8 @@ init_module!(CSV_MODULE,
csv_next, csv_next,
csv_eof, csv_eof,
csv_column, csv_column,
csv_rowid); csv_rowid
);
/// An instance of the CSV virtual table /// An instance of the CSV virtual table
#[repr(C)] #[repr(C)]
@ -61,7 +66,8 @@ struct CSVTab {
impl CSVTab { impl CSVTab {
fn reader(&self) -> result::Result<csv::Reader<File>, csv::Error> { fn reader(&self) -> result::Result<csv::Reader<File>, csv::Error> {
csv::Reader::from_file(&self.filename).map(|reader| { csv::Reader::from_file(&self.filename).map(|reader| {
reader.has_headers(self.has_headers) reader
.has_headers(self.has_headers)
.delimiter(self.delimiter) .delimiter(self.delimiter)
.quote(self.quote) .quote(self.quote)
}) })
@ -114,39 +120,55 @@ impl VTab for CSVTab {
match param { match param {
"filename" => { "filename" => {
if !Path::new(value).exists() { if !Path::new(value).exists() {
return Err(Error::ModuleError(format!("file '{}' does not exist", value))); return Err(Error::ModuleError(format!(
"file '{}' does not exist",
value
)));
} }
vtab.filename = value.to_owned(); vtab.filename = value.to_owned();
}, }
"schema" => { "schema" => {
schema = Some(value.to_owned()); schema = Some(value.to_owned());
}, }
"columns" => { "columns" => {
if let Ok(n) = value.parse::<u16>() { if let Ok(n) = value.parse::<u16>() {
if n_col.is_some() { if n_col.is_some() {
return Err(Error::ModuleError("more than one 'columns' parameter".to_owned())); return Err(Error::ModuleError(
"more than one 'columns' parameter".to_owned(),
));
} else if n == 0 { } else if n == 0 {
return Err(Error::ModuleError("must have at least one column".to_owned())); return Err(Error::ModuleError(
"must have at least one column".to_owned(),
));
} }
n_col = Some(n); n_col = Some(n);
} else { } else {
return Err(Error::ModuleError(format!("unrecognized argument to 'columns': {}", value))); return Err(Error::ModuleError(format!(
"unrecognized argument to 'columns': {}",
value
)));
}
} }
},
"header" => { "header" => {
if let Some(b) = parse_boolean(value) { if let Some(b) = parse_boolean(value) {
vtab.has_headers = b; vtab.has_headers = b;
} else { } else {
return Err(Error::ModuleError(format!("unrecognized argument to 'header': {}", value))); return Err(Error::ModuleError(format!(
"unrecognized argument to 'header': {}",
value
)));
}
} }
},
"delimiter" => { "delimiter" => {
if let Some(b) = CSVTab::parse_byte(value) { if let Some(b) = CSVTab::parse_byte(value) {
vtab.delimiter = b; vtab.delimiter = b;
} else { } else {
return Err(Error::ModuleError(format!("unrecognized argument to 'delimiter': {}", value))); return Err(Error::ModuleError(format!(
"unrecognized argument to 'delimiter': {}",
value
)));
}
} }
},
"quote" => { "quote" => {
if let Some(b) = CSVTab::parse_byte(value) { if let Some(b) = CSVTab::parse_byte(value) {
if b == b'0' { if b == b'0' {
@ -155,12 +177,18 @@ impl VTab for CSVTab {
vtab.quote = b; vtab.quote = b;
} }
} else { } else {
return Err(Error::ModuleError(format!("unrecognized argument to 'quote': {}", value))); return Err(Error::ModuleError(format!(
"unrecognized argument to 'quote': {}",
value
)));
}
} }
},
_ => { _ => {
return Err(Error::ModuleError(format!("unrecognized parameter '{}'", param))); return Err(Error::ModuleError(format!(
}, "unrecognized parameter '{}'",
param
)));
}
} }
} }
@ -176,7 +204,10 @@ impl VTab for CSVTab {
vtab.offset_first_row = reader.byte_offset(); vtab.offset_first_row = reader.byte_offset();
// headers ignored if cols is not empty // headers ignored if cols is not empty
if n_col.is_none() && schema.is_none() { if n_col.is_none() && schema.is_none() {
cols = headers.into_iter().map(|header| escape_double_quote(&header).into_owned()).collect(); cols = headers
.into_iter()
.map(|header| escape_double_quote(&header).into_owned())
.collect();
} }
} else { } else {
let mut count = 0; let mut count = 0;
@ -257,11 +288,7 @@ impl VTabCursor for CSVTabCursor {
// Only a full table scan is supported. So `filter` simply rewinds to // Only a full table scan is supported. So `filter` simply rewinds to
// the beginning. // the beginning.
fn filter(&mut self, fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> {
_idx_num: c_int,
_idx_str: Option<&str>,
_args: &Values)
-> Result<()> {
{ {
let offset_first_row = self.vtab().offset_first_row; let offset_first_row = self.vtab().offset_first_row;
try!(self.reader.seek(offset_first_row)); try!(self.reader.seek(offset_first_row));
@ -290,7 +317,10 @@ impl VTabCursor for CSVTabCursor {
} }
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> { fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
if col < 0 || col as usize >= self.cols.len() { if col < 0 || col as usize >= self.cols.len() {
return Err(Error::ModuleError(format!("column index out of bounds: {}", col))); return Err(Error::ModuleError(format!(
"column index out of bounds: {}",
col
)));
} }
if self.cols.is_empty() { if self.cols.is_empty() {
ctx.set_result(&Null); ctx.set_result(&Null);
@ -314,14 +344,15 @@ impl From<csv::Error> for Error {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use {Connection, Result};
use vtab::csvtab; use vtab::csvtab;
use {Connection, Result};
#[test] #[test]
fn test_csv_module() { fn test_csv_module() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap(); csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)").unwrap(); db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.unwrap();
{ {
let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap(); let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
@ -330,8 +361,9 @@ mod test {
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers); assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
} }
let ids: Result<Vec<i32>> = let ids: Result<Vec<i32>> = s.query_map(&[], |row| row.get::<i32, i32>(0))
s.query_map(&[], |row| row.get::<i32, i32>(0)).unwrap().collect(); .unwrap()
.collect();
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id); let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id);
assert_eq!(sum, 15); assert_eq!(sum, 15);
} }
@ -342,13 +374,14 @@ mod test {
fn test_csv_cursor() { fn test_csv_cursor() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap(); csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)").unwrap(); db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.unwrap();
{ {
let mut s = let mut s = db.prepare(
db.prepare("SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \ "SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
v1.rowid < v2.rowid") v1.rowid < v2.rowid",
.unwrap(); ).unwrap();
let mut rows = s.query(&[]).unwrap(); let mut rows = s.query(&[]).unwrap();
let row = rows.next().unwrap().unwrap(); let row = rows.next().unwrap().unwrap();

View File

@ -5,9 +5,9 @@ use std::default::Default;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::rc::Rc; use std::rc::Rc;
use {Connection, Error, Result};
use ffi; use ffi;
use vtab::{declare_vtab, escape_double_quote, Context, IndexInfo, Values, VTab, VTabCursor}; use vtab::{declare_vtab, escape_double_quote, Context, IndexInfo, VTab, VTabCursor, Values};
use {Connection, Error, Result};
/// Create a specific instance of an intarray object. /// Create a specific instance of an intarray object.
/// The new intarray object is returned. /// The new intarray object is returned.
@ -17,8 +17,10 @@ use vtab::{declare_vtab, escape_double_quote, Context, IndexInfo, Values, VTab,
pub fn create_int_array(conn: &Connection, name: &str) -> Result<Rc<RefCell<Vec<i64>>>> { pub fn create_int_array(conn: &Connection, name: &str) -> Result<Rc<RefCell<Vec<i64>>>> {
let array = Rc::new(RefCell::new(Vec::new())); let array = Rc::new(RefCell::new(Vec::new()));
try!(conn.create_module(name, &INT_ARRAY_MODULE, Some(array.clone()))); try!(conn.create_module(name, &INT_ARRAY_MODULE, Some(array.clone())));
try!(conn.execute_batch(&format!("CREATE VIRTUAL TABLE temp.\"{0}\" USING \"{0}\"", try!(conn.execute_batch(&format!(
escape_double_quote(name)))); "CREATE VIRTUAL TABLE temp.\"{0}\" USING \"{0}\"",
escape_double_quote(name)
)));
Ok(array) Ok(array)
} }
@ -28,15 +30,18 @@ pub fn create_int_array(conn: &Connection, name: &str) -> Result<Rc<RefCell<Vec<
/// In fact, the intarray is not destroy until the connection is closed /// In fact, the intarray is not destroy until the connection is closed
/// because there is no other way to destroy the associated module. /// because there is no other way to destroy the associated module.
pub fn drop_int_array(conn: &Connection, name: &str) -> Result<()> { pub fn drop_int_array(conn: &Connection, name: &str) -> Result<()> {
conn.execute_batch(&format!("DROP TABLE temp.\"{0}\"", escape_double_quote(name))) conn.execute_batch(&format!(
"DROP TABLE temp.\"{0}\"",
escape_double_quote(name)
))
// http://www.mail-archive.com/sqlite-users%40mailinglists.sqlite.org/msg08423.html // http://www.mail-archive.com/sqlite-users%40mailinglists.sqlite.org/msg08423.html
// "Once a virtual table module has been created, it cannot be modified or destroyed, except by closing the database connection." // "Once a virtual table module has been created, it cannot be modified or destroyed, except by closing the database connection."
// let aux: Option<()> = None; // let aux: Option<()> = None;
// conn.create_module(name, ptr::null() as *const ffi::sqlite3_module, aux) // conn.create_module(name, ptr::null() as *const ffi::sqlite3_module, aux)
} }
eponymous_module!(INT_ARRAY_MODULE, eponymous_module!(
INT_ARRAY_MODULE,
IntArrayVTab, IntArrayVTab,
IntArrayVTabCursor, IntArrayVTabCursor,
Some(int_array_connect), Some(int_array_connect),
@ -50,7 +55,8 @@ eponymous_module!(INT_ARRAY_MODULE,
int_array_next, int_array_next,
int_array_eof, int_array_eof,
int_array_column, int_array_column,
int_array_rowid); int_array_rowid
);
#[repr(C)] #[repr(C)]
struct IntArrayVTab { struct IntArrayVTab {
@ -62,16 +68,20 @@ struct IntArrayVTab {
impl VTab for IntArrayVTab { impl VTab for IntArrayVTab {
type Cursor = IntArrayVTabCursor; type Cursor = IntArrayVTabCursor;
unsafe fn connect(db: *mut ffi::sqlite3, unsafe fn connect(
db: *mut ffi::sqlite3,
aux: *mut c_void, aux: *mut c_void,
_args: &[&[u8]]) _args: &[&[u8]],
-> Result<IntArrayVTab> { ) -> Result<IntArrayVTab> {
let array = aux as *const Rc<RefCell<Vec<i64>>>; let array = aux as *const Rc<RefCell<Vec<i64>>>;
let vtab = IntArrayVTab { let vtab = IntArrayVTab {
base: Default::default(), base: Default::default(),
array, array,
}; };
try!(declare_vtab(db, "CREATE TABLE x(value INTEGER PRIMARY KEY)")); try!(declare_vtab(
db,
"CREATE TABLE x(value INTEGER PRIMARY KEY)"
));
Ok(vtab) Ok(vtab)
} }
@ -108,11 +118,7 @@ impl VTabCursor for IntArrayVTabCursor {
fn vtab(&self) -> &IntArrayVTab { fn vtab(&self) -> &IntArrayVTab {
unsafe { &*(self.base.pVtab as *const IntArrayVTab) } unsafe { &*(self.base.pVtab as *const IntArrayVTab) }
} }
fn filter(&mut self, fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> {
_idx_num: c_int,
_idx_str: Option<&str>,
_args: &Values)
-> Result<()> {
self.i = 0; self.i = 0;
Ok(()) Ok(())
} }
@ -142,8 +148,8 @@ impl VTabCursor for IntArrayVTabCursor {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use vtab::int_array; use vtab::int_array;
use Connection;
#[test] #[test]
#[cfg_attr(rustfmt, rustfmt_skip)] #[cfg_attr(rustfmt, rustfmt_skip)]

View File

@ -6,11 +6,11 @@ use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use {Connection, Error, Result, InnerConnection, str_to_cstring};
use error::error_from_sqlite_code; use error::error_from_sqlite_code;
use ffi; use ffi;
use functions::{set_result, report_error}; use functions::{report_error, set_result};
use types::{FromSql, FromSqlError, ToSql, ValueRef}; use types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result};
// let conn: Connection = ...; // let conn: Connection = ...;
// let mod: Module = ...; // VTab builder // let mod: Module = ...; // VTab builder
@ -77,7 +77,9 @@ impl IndexInfo {
pub fn constraints(&self) -> IndexConstraintIter { pub fn constraints(&self) -> IndexConstraintIter {
let constraints = let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) }; unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
IndexConstraintIter { iter: constraints.iter() } IndexConstraintIter {
iter: constraints.iter(),
}
} }
/// Number of terms in the ORDER BY clause /// Number of terms in the ORDER BY clause
@ -241,7 +243,9 @@ impl<'a> Values<'a> {
} }
pub fn iter(&self) -> ValueIter { pub fn iter(&self) -> ValueIter {
ValueIter { iter: self.args.iter() } ValueIter {
iter: self.args.iter(),
}
} }
} }
@ -262,7 +266,9 @@ impl<'a> Iterator for ValueIter<'a> {
type Item = ValueRef<'a>; type Item = ValueRef<'a>;
fn next(&mut self) -> Option<ValueRef<'a>> { fn next(&mut self) -> Option<ValueRef<'a>> {
self.iter.next().map(|&raw| { unsafe { ValueRef::from_value(raw) } }) self.iter
.next()
.map(|&raw| unsafe { ValueRef::from_value(raw) })
} }
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
@ -272,41 +278,45 @@ 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>(&self, pub fn create_module<A>(
&self,
module_name: &str, module_name: &str,
module: *const ffi::sqlite3_module, module: *const ffi::sqlite3_module,
aux: Option<A>) aux: Option<A>,
-> Result<()> { ) -> Result<()> {
self.db self.db.borrow_mut().create_module(module_name, module, aux)
.borrow_mut()
.create_module(module_name, module, aux)
} }
} }
impl InnerConnection { impl InnerConnection {
fn create_module<A>(&mut self, fn create_module<A>(
&mut self,
module_name: &str, module_name: &str,
module: *const ffi::sqlite3_module, module: *const ffi::sqlite3_module,
aux: Option<A>) aux: Option<A>,
-> 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 A = Box::into_raw(Box::new(aux));
unsafe { unsafe {
ffi::sqlite3_create_module_v2(self.db(), ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(), c_name.as_ptr(),
module, module,
boxed_aux as *mut c_void, boxed_aux as *mut c_void,
Some(free_boxed_value::<A>)) Some(free_boxed_value::<A>),
)
} }
} }
None => unsafe { None => unsafe {
ffi::sqlite3_create_module_v2(self.db(), ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(), c_name.as_ptr(),
module, module,
ptr::null_mut(), ptr::null_mut(),
None) None,
)
}, },
}; };
self.decode_result(r) self.decode_result(r)
@ -339,11 +349,9 @@ pub fn dequote(s: &str) -> &str {
return s; return s;
} }
match s.bytes().next() { match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => { Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
match s.bytes().rev().next() {
Some(e) if e == b => &s[1..s.len() - 1], Some(e) if e == b => &s[1..s.len() - 1],
_ => s, _ => s,
}
}, },
_ => s, _ => s,
} }
@ -354,9 +362,13 @@ pub fn dequote(s: &str) -> &str {
/// 0 no false off /// 0 no false off
/// ``` /// ```
pub fn parse_boolean(s: &str) -> Option<bool> { pub fn parse_boolean(s: &str) -> Option<bool> {
if s.eq_ignore_ascii_case("yes") || s.eq_ignore_ascii_case("on") || s.eq_ignore_ascii_case("true") || s.eq("1") { if s.eq_ignore_ascii_case("yes") || s.eq_ignore_ascii_case("on")
|| s.eq_ignore_ascii_case("true") || s.eq("1")
{
Some(true) Some(true)
} else if s.eq_ignore_ascii_case("no") || s.eq_ignore_ascii_case("off") || s.eq_ignore_ascii_case("false") || s.eq("0") { } else if s.eq_ignore_ascii_case("no") || s.eq_ignore_ascii_case("off")
|| s.eq_ignore_ascii_case("false") || s.eq("0")
{
Some(false) Some(false)
} else { } else {
None None
@ -370,13 +382,23 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
#[macro_export] #[macro_export]
macro_rules! init_module { macro_rules! init_module {
($module_name: ident, $vtab: ident, $cursor: ty, (
$create: ident, $connect: ident, $best_index: ident, $module_name:ident,
$disconnect: ident, $destroy: ident, $vtab:ident,
$open: ident, $close: ident, $cursor:ty,
$filter: ident, $next: ident, $eof: ident, $create:ident,
$column: ident, $rowid: 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 { static $module_name: ffi::sqlite3_module = ffi::sqlite3_module {
iVersion: 1, iVersion: 1,
xCreate: Some($create), xCreate: Some($create),
@ -406,25 +428,43 @@ static $module_name: ffi::sqlite3_module = ffi::sqlite3_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, $create, create); create_or_connect!($vtab, $create, create);
common_decl!($vtab, $cursor, common_decl!(
$connect, $best_index, $vtab,
$disconnect, $destroy, $cursor,
$open, $close, $connect,
$filter, $next, $eof, $best_index,
$column, $rowid $disconnect,
$destroy,
$open,
$close,
$filter,
$next,
$eof,
$column,
$rowid
); );
} };
} // init_module macro end } // init_module macro end
#[macro_export] #[macro_export]
macro_rules! eponymous_module { macro_rules! eponymous_module {
($module_name: ident, $vtab: ident, $cursor: ty, (
$create: expr, $connect: ident, $best_index: ident, $module_name:ident,
$disconnect: ident, $destroy: expr, $vtab:ident,
$open: ident, $close: ident, $cursor:ty,
$filter: ident, $next: ident, $eof: ident, $create:expr,
$column: ident, $rowid: ident) => { $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 { static $module_name: ffi::sqlite3_module = ffi::sqlite3_module {
iVersion: 1, iVersion: 1,
xCreate: $create, /* For eponymous-only virtual tables, the xCreate method is NULL */ xCreate: $create, /* For eponymous-only virtual tables, the xCreate method is NULL */
@ -452,65 +492,84 @@ static $module_name: ffi::sqlite3_module = ffi::sqlite3_module {
xRollbackTo: None, xRollbackTo: None,
}; };
common_decl!($vtab, $cursor, common_decl!(
$connect, $best_index, $vtab,
$disconnect, $destroy, $cursor,
$open, $close, $connect,
$filter, $next, $eof, $best_index,
$column, $rowid $disconnect,
$destroy,
$open,
$close,
$filter,
$next,
$eof,
$column,
$rowid
); );
} };
} // eponymous_module macro end } // eponymous_module macro end
macro_rules! create_or_connect { macro_rules! create_or_connect {
($vtab:ident, $create_or_connect:ident, $vtab_func:ident) => { ($vtab:ident, $create_or_connect:ident, $vtab_func:ident) => {
unsafe extern "C" fn $create_or_connect(db: *mut ffi::sqlite3, unsafe extern "C" fn $create_or_connect(
db: *mut ffi::sqlite3,
aux: *mut c_void, aux: *mut c_void,
argc: c_int, argc: c_int,
argv: *const *const c_char, argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab, pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char) err_msg: *mut *mut c_char,
-> c_int { ) -> c_int {
use std::error::Error as StdError; use std::error::Error as StdError;
use std::ffi::CStr; use std::ffi::CStr;
use std::slice; use std::slice;
use vtab::mprintf; use vtab::mprintf;
let args = slice::from_raw_parts(argv, argc as usize); let args = slice::from_raw_parts(argv, argc as usize);
let vec = args.iter().map(|&cs| { let vec = args.iter()
CStr::from_ptr(cs).to_bytes() .map(|&cs| CStr::from_ptr(cs).to_bytes())
}).collect::<Vec<_>>(); .collect::<Vec<_>>();
match $vtab::$vtab_func(db, aux, &vec[..]) { match $vtab::$vtab_func(db, aux, &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;
ffi::SQLITE_OK ffi::SQLITE_OK
}, }
Err(Error::SqliteFailure(err, s)) => { Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s { if let Some(s) = s {
*err_msg = mprintf(&s); *err_msg = mprintf(&s);
} }
err.extended_code err.extended_code
}, }
Err(err) => { Err(err) => {
*err_msg = mprintf(err.description()); *err_msg = mprintf(err.description());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
} }
} };
} // create_or_connect macro end } // create_or_connect macro end
macro_rules! common_decl { macro_rules! common_decl {
($vtab: ident, $cursor: ty, (
$connect: ident, $best_index: ident, $vtab:ident,
$disconnect: ident, $destroy: expr, $cursor:ty,
$open: ident, $close: ident, $connect:ident,
$filter: ident, $next: ident, $eof: ident, $best_index:ident,
$column: ident, $rowid: ident) => { $disconnect:ident,
$destroy:expr,
$open:ident,
$close:ident,
$filter:ident,
$next:ident,
$eof:ident,
$column:ident,
$rowid:ident
) => {
create_or_connect!($vtab, $connect, connect); create_or_connect!($vtab, $connect, connect);
unsafe extern "C" fn $best_index(vtab: *mut ffi::sqlite3_vtab, unsafe extern "C" fn $best_index(
info: *mut ffi::sqlite3_index_info) vtab: *mut ffi::sqlite3_vtab,
-> c_int { info: *mut ffi::sqlite3_index_info,
) -> c_int {
use std::error::Error as StdError; use std::error::Error as StdError;
use vtab::set_err_msg; use vtab::set_err_msg;
let vt = vtab as *mut $vtab; let vt = vtab as *mut $vtab;
@ -522,14 +581,12 @@ unsafe extern "C" fn $best_index(vtab: *mut ffi::sqlite3_vtab,
set_err_msg(vtab, &err_msg); set_err_msg(vtab, &err_msg);
} }
err.extended_code err.extended_code
}, }
Err(err) => { Err(err) => {
set_err_msg(vtab, err.description()); set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
} }
} }
} }
unsafe extern "C" fn $disconnect(vtab: *mut ffi::sqlite3_vtab) -> c_int { unsafe extern "C" fn $disconnect(vtab: *mut ffi::sqlite3_vtab) -> c_int {
let vtab = vtab as *mut $vtab; let vtab = vtab as *mut $vtab;
@ -537,9 +594,10 @@ unsafe extern "C" fn $disconnect(vtab: *mut ffi::sqlite3_vtab) -> c_int {
ffi::SQLITE_OK ffi::SQLITE_OK
} }
unsafe extern "C" fn $open(vtab: *mut ffi::sqlite3_vtab, unsafe extern "C" fn $open(
pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor) vtab: *mut ffi::sqlite3_vtab,
-> c_int { pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor,
) -> c_int {
use std::error::Error as StdError; use std::error::Error as StdError;
use vtab::set_err_msg; use vtab::set_err_msg;
let vt = vtab as *mut $vtab; let vt = vtab as *mut $vtab;
@ -548,13 +606,13 @@ unsafe extern "C" fn $open(vtab: *mut ffi::sqlite3_vtab,
let boxed_cursor: *mut $cursor = Box::into_raw(Box::new(cursor)); let boxed_cursor: *mut $cursor = Box::into_raw(Box::new(cursor));
*pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor; *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
ffi::SQLITE_OK ffi::SQLITE_OK
}, }
Err(Error::SqliteFailure(err, s)) => { Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s { if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg); set_err_msg(vtab, &err_msg);
} }
err.extended_code err.extended_code
}, }
Err(err) => { Err(err) => {
set_err_msg(vtab, err.description()); set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR ffi::SQLITE_ERROR
@ -567,12 +625,13 @@ unsafe extern "C" fn $close(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int {
ffi::SQLITE_OK ffi::SQLITE_OK
} }
unsafe extern "C" fn $filter(cursor: *mut ffi::sqlite3_vtab_cursor, unsafe extern "C" fn $filter(
cursor: *mut ffi::sqlite3_vtab_cursor,
idx_num: c_int, idx_num: c_int,
idx_str: *const c_char, idx_str: *const c_char,
argc: c_int, argc: c_int,
argv: *mut *mut ffi::sqlite3_value) argv: *mut *mut ffi::sqlite3_value,
-> c_int { ) -> c_int {
use std::ffi::CStr; use std::ffi::CStr;
use std::slice; use std::slice;
use std::str; use std::str;
@ -597,35 +656,35 @@ unsafe extern "C" fn $eof(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int {
let cr = cursor as *mut $cursor; let cr = cursor as *mut $cursor;
(*cr).eof() as c_int (*cr).eof() as c_int
} }
unsafe extern "C" fn $column(cursor: *mut ffi::sqlite3_vtab_cursor, unsafe extern "C" fn $column(
cursor: *mut ffi::sqlite3_vtab_cursor,
ctx: *mut ffi::sqlite3_context, ctx: *mut ffi::sqlite3_context,
i: c_int) i: c_int,
-> c_int { ) -> c_int {
use vtab::{result_error, Context}; use vtab::{result_error, Context};
let cr = cursor as *mut $cursor; let cr = cursor as *mut $cursor;
let mut ctxt = Context(ctx); let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i)) result_error(ctx, (*cr).column(&mut ctxt, i))
} }
unsafe extern "C" fn $rowid(cursor: *mut ffi::sqlite3_vtab_cursor, unsafe extern "C" fn $rowid(
p_rowid: *mut ffi::sqlite3_int64) cursor: *mut ffi::sqlite3_vtab_cursor,
-> c_int { p_rowid: *mut ffi::sqlite3_int64,
) -> c_int {
use vtab::cursor_error; use vtab::cursor_error;
let cr = cursor as *mut $cursor; let cr = cursor as *mut $cursor;
match (*cr).rowid() { match (*cr).rowid() {
Ok(rowid) => { Ok(rowid) => {
*p_rowid = rowid; *p_rowid = rowid;
ffi::SQLITE_OK ffi::SQLITE_OK
}, }
err => cursor_error(cursor, err) err => cursor_error(cursor, err),
}
} }
} }
};
} // common_decl macro end } // common_decl macro end
/// Virtual table cursors can set an error message by assigning a string to `zErrMsg`. /// Virtual table cursors can set an error message by assigning a string to `zErrMsg`.
pub unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, pub unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
result: Result<T>)
-> c_int {
use std::error::Error as StdError; use std::error::Error as StdError;
match result { match result {
Ok(_) => ffi::SQLITE_OK, Ok(_) => ffi::SQLITE_OK,
@ -691,9 +750,9 @@ pub fn mprintf(err_msg: &str) -> *mut c_char {
unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) } unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) }
} }
pub mod int_array;
#[cfg(feature = "csvtab")] #[cfg(feature = "csvtab")]
pub mod csvtab; pub mod csvtab;
pub mod int_array;
#[cfg(feature = "bundled")] #[cfg(feature = "bundled")]
pub mod series; pub mod series;

View File

@ -3,9 +3,9 @@
use std::default::Default; use std::default::Default;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use {Connection, Error, Result};
use ffi; use ffi;
use vtab::{self, declare_vtab, Context, IndexInfo, Values, VTab, VTabCursor}; use vtab::{self, declare_vtab, Context, IndexInfo, VTab, VTabCursor, Values};
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<()> {
@ -13,7 +13,8 @@ pub fn load_module(conn: &Connection) -> Result<()> {
conn.create_module("generate_series", &SERIES_MODULE, aux) conn.create_module("generate_series", &SERIES_MODULE, aux)
} }
eponymous_module!(SERIES_MODULE, eponymous_module!(
SERIES_MODULE,
SeriesTab, SeriesTab,
SeriesTabCursor, SeriesTabCursor,
None, None,
@ -27,7 +28,8 @@ eponymous_module!(SERIES_MODULE,
series_next, series_next,
series_eof, series_eof,
series_column, series_column,
series_rowid); series_rowid
);
// Column numbers // Column numbers
// const SERIES_COLUMN_VALUE : c_int = 0; // const SERIES_COLUMN_VALUE : c_int = 0;
@ -51,7 +53,6 @@ bitflags! {
} }
} }
/// An instance of the Series virtual table /// An instance of the Series virtual table
#[repr(C)] #[repr(C)]
struct SeriesTab { struct SeriesTab {
@ -59,17 +60,21 @@ struct SeriesTab {
base: ffi::sqlite3_vtab, base: ffi::sqlite3_vtab,
} }
impl VTab for SeriesTab { impl VTab for SeriesTab {
type Cursor = SeriesTabCursor; type Cursor = SeriesTabCursor;
unsafe fn connect(db: *mut ffi::sqlite3, unsafe fn connect(
db: *mut ffi::sqlite3,
_aux: *mut c_void, _aux: *mut c_void,
_args: &[&[u8]]) _args: &[&[u8]],
-> Result<SeriesTab> { ) -> Result<SeriesTab> {
let vtab = SeriesTab { base: Default::default() }; let vtab = SeriesTab {
try!(declare_vtab(db, base: Default::default(),
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)")); };
try!(declare_vtab(
db,
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)"
));
Ok(vtab) Ok(vtab)
} }
@ -127,7 +132,13 @@ impl VTab for SeriesTab {
} }
if idx_num.contains(QueryPlanFlags::BOTH) { if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available. // Both start= and stop= boundaries are available.
info.set_estimated_cost((2 - if idx_num.contains(QueryPlanFlags::STEP) { 1 } else { 0 }) as f64); info.set_estimated_cost(
(2 - if idx_num.contains(QueryPlanFlags::STEP) {
1
} else {
0
}) as f64,
);
info.set_estimated_rows(1000); info.set_estimated_rows(1000);
if info.num_of_order_by() == 1 { if info.num_of_order_by() == 1 {
if info.is_order_by_desc(0) { if info.is_order_by_desc(0) {
@ -179,11 +190,7 @@ impl VTabCursor for SeriesTabCursor {
fn vtab(&self) -> &SeriesTab { fn vtab(&self) -> &SeriesTab {
unsafe { &*(self.base.pVtab as *const SeriesTab) } unsafe { &*(self.base.pVtab as *const SeriesTab) }
} }
fn filter(&mut self, fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
idx_num: c_int,
_idx_str: Option<&str>,
args: &Values)
-> Result<()> {
let idx_num = QueryPlanFlags::from_bits_truncate(idx_num); let idx_num = QueryPlanFlags::from_bits_truncate(idx_num);
let mut i = 0; let mut i = 0;
if idx_num.contains(QueryPlanFlags::START) { if idx_num.contains(QueryPlanFlags::START) {
@ -251,9 +258,9 @@ impl VTabCursor for SeriesTabCursor {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection;
use vtab::series;
use ffi; use ffi;
use vtab::series;
use Connection;
#[test] #[test]
fn test_series_module() { fn test_series_module() {
@ -267,9 +274,7 @@ mod test {
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap(); let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
let series = s.query_map(&[], |row| row.get::<i32, i32>(0)).unwrap();
let series = s.query_map(&[], |row| row.get::<i32, i32>(0))
.unwrap();
let mut expected = 0; let mut expected = 0;
for value in series { for value in series {