diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index cabbfc1..079c3f5 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -20,9 +20,11 @@ pub fn load_module(conn: &Connection) -> Result<()> { init_module!(CSV_MODULE, CSVTab, CSVTabCursor, - csv_create, + Some(csv_connect), + csv_connect, csv_best_index, - csv_destroy, + csv_disconnect, + Some(csv_disconnect), csv_open, csv_close, csv_filter, @@ -56,7 +58,7 @@ impl CSVTab { } impl VTab for CSVTab { - fn create(db: *mut ffi::sqlite3, _aux: *mut libc::c_void, args: &[&[u8]]) -> Result { + fn connect(db: *mut ffi::sqlite3, _aux: *mut libc::c_void, args: &[&[u8]]) -> Result { if args.len() < 4 { return Err(Error::ModuleError("no CSV file specified".to_owned())); } diff --git a/src/vtab/int_array.rs b/src/vtab/int_array.rs index a4183aa..682736c 100644 --- a/src/vtab/int_array.rs +++ b/src/vtab/int_array.rs @@ -1,4 +1,5 @@ //! Int array virtual table. +//! Port of C ["intarray"](http://www.sqlite.org/cgi/src/finfo?name=src/test_intarray.h). use std::cell::RefCell; use std::default::Default; use std::mem; @@ -39,9 +40,11 @@ pub fn drop_int_array(conn: &Connection, name: &str) -> Result<()> { init_module!(INT_ARRAY_MODULE, IntArrayVTab, IntArrayVTabCursor, - int_array_create, + Some(int_array_connect), + int_array_connect, int_array_best_index, - int_array_destroy, + int_array_disconnect, + Some(int_array_disconnect), int_array_open, int_array_close, int_array_filter, @@ -58,10 +61,10 @@ struct IntArrayVTab { } impl VTab for IntArrayVTab { - fn create(db: *mut ffi::sqlite3, - aux: *mut libc::c_void, - _args: &[&[u8]]) - -> Result { + fn connect(db: *mut ffi::sqlite3, + aux: *mut libc::c_void, + _args: &[&[u8]]) + -> Result { let array = unsafe { mem::transmute(aux) }; let vtab = IntArrayVTab { base: Default::default(), diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index 106e544..3b97572 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -44,7 +44,7 @@ use types::FromSql; pub trait VTab>: Sized { /// 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 CREATE VIRTUAL TABLE statement. - fn create(db: *mut ffi::sqlite3, aux: *mut libc::c_void, args: &[&[u8]]) -> Result; + fn connect(db: *mut ffi::sqlite3, aux: *mut libc::c_void, args: &[&[u8]]) -> Result; /// 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. @@ -90,7 +90,7 @@ impl IndexInfo { } } /// True if this constraint is usable - pub fn constraint_usable(&self, constraint_idx: usize) -> bool { + pub fn is_constraint_usable(&self, constraint_idx: usize) -> bool { use std::slice; unsafe { let constraints = slice::from_raw_parts((*self.0).aConstraint, @@ -288,18 +288,19 @@ unsafe extern "C" fn free_boxed_value(p: *mut libc::c_void) { #[macro_export] macro_rules! init_module { ($module_name: ident, $vtab: ident, $cursor: ty, - $create: ident, $best_index: ident, $destroy: ident, + $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: Some($create), - xConnect: Some($create), /* A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method */ + xCreate: $create, + 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($destroy), - xDestroy: Some($destroy), + xDisconnect: Some($disconnect), + xDestroy: $destroy, xOpen: Some($open), xClose: Some($close), xFilter: Some($filter), @@ -319,7 +320,7 @@ static $module_name: ffi::sqlite3_module = ffi::sqlite3_module { xRollbackTo: None, }; -unsafe extern "C" fn $create(db: *mut ffi::sqlite3, +unsafe extern "C" fn $connect(db: *mut ffi::sqlite3, aux: *mut libc::c_void, argc: libc::c_int, argv: *const *const libc::c_char, @@ -334,7 +335,7 @@ unsafe extern "C" fn $create(db: *mut ffi::sqlite3, let vec = args.iter().map(|cs| { CStr::from_ptr(*cs).to_bytes() }).collect::>(); - match $vtab::create(db, aux, &vec[..]) { + match $vtab::connect(db, aux, &vec[..]) { Ok(vtab) => { let boxed_vtab: *mut $vtab = Box::into_raw(Box::new(vtab)); *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; @@ -375,7 +376,7 @@ unsafe extern "C" fn $best_index(vtab: *mut ffi::sqlite3_vtab, } } -unsafe extern "C" fn $destroy(vtab: *mut ffi::sqlite3_vtab) -> libc::c_int { +unsafe extern "C" fn $disconnect(vtab: *mut ffi::sqlite3_vtab) -> libc::c_int { let vtab = vtab as *mut $vtab; let _: Box<$vtab> = Box::from_raw(vtab); ffi::SQLITE_OK @@ -536,3 +537,4 @@ pub fn mprintf(err_msg: &str) -> *mut ::libc::c_char { pub mod int_array; #[cfg(feature = "csvtab")] pub mod csvtab; +pub mod series; diff --git a/src/vtab/series.rs b/src/vtab/series.rs new file mode 100644 index 0000000..550277f --- /dev/null +++ b/src/vtab/series.rs @@ -0,0 +1,244 @@ +//! 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 libc; + +use {Connection, Error, Result}; +use ffi; +use vtab::{self, declare_vtab, Context, IndexInfo, Values, VTab, VTabCursor}; + +/// Register the "generate_series" module. +pub fn load_module(conn: &Connection) -> Result<()> { + let aux: Option<()> = None; + conn.create_module("generate_series", &SERIES_MODULE, aux) +} + + +init_module!(SERIES_MODULE, + 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); + +// Column numbers +// const SERIES_COLUMN_VALUE : libc::c_int = 0; +const SERIES_COLUMN_START: libc::c_int = 1; +const SERIES_COLUMN_STOP: libc::c_int = 2; +const SERIES_COLUMN_STEP: libc::c_int = 3; + +bitflags! { + #[repr(C)] + flags QueryPlanFlags: ::libc::c_int { + // start = $value -- constraint exists + const START = 1, + // stop = $value -- constraint exists + const STOP = 2, + // step = $value -- constraint exists + const STEP = 4, + // output in descending order + const DESC = 8, + // Both start and stop + const BOTH = START.bits | STOP.bits, + } +} + + +/// An instance of the Series virtual table +#[repr(C)] +struct SeriesTab { + /// Base class. Must be first + base: ffi::sqlite3_vtab, +} + + +impl VTab for SeriesTab { + fn connect(db: *mut ffi::sqlite3, + _aux: *mut libc::c_void, + _args: &[&[u8]]) + -> Result { + 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<()> { + // The query plan bitmask + let mut idx_num: QueryPlanFlags = QueryPlanFlags::empty(); + // Index of the start= constraint + let mut start_idx = None; + // Index of the stop= constraint + let mut stop_idx = None; + // Index of the step= constraint + let mut step_idx = None; + for i in 0..info.num_of_constraint() { + if !info.is_constraint_usable(i) { + continue; + } + if info.constraint_operator(i) != vtab::SQLITE_INDEX_CONSTRAINT_EQ { + continue; + } + match info.constraint_column(i) { + SERIES_COLUMN_START => { + start_idx = Some(i); + idx_num |= START; + } + SERIES_COLUMN_STOP => { + stop_idx = Some(i); + idx_num |= STOP; + } + SERIES_COLUMN_STEP => { + step_idx = Some(i); + idx_num |= STEP; + } + _ => {} + }; + } + + let mut num_of_arg = 0; + if let Some(start_idx) = start_idx { + num_of_arg += 1; + info.set_argv_index(start_idx, num_of_arg); + info.set_omit(start_idx, true); + } + if let Some(stop_idx) = stop_idx { + num_of_arg += 1; + info.set_argv_index(stop_idx, num_of_arg); + info.set_omit(stop_idx, true); + } + if let Some(step_idx) = step_idx { + num_of_arg += 1; + info.set_argv_index(step_idx, num_of_arg); + info.set_omit(step_idx, true); + } + if idx_num.contains(BOTH) { + // Both start= and stop= boundaries are available. + info.set_estimated_cost((2 - if idx_num.contains(STEP) { 1 } else { 0 }) as f64); + info.set_estimated_rows(1000); + if info.num_of_order_by() == 1 { + if info.is_order_by_desc(0) { + idx_num |= DESC; + } + info.set_order_by_consumed(true); + } + } else { + info.set_estimated_cost(2147483647f64); + info.set_estimated_rows(2147483647); + } + info.set_idx_num(idx_num.bits()); + Ok(()) + } + + fn open(&self) -> Result { + Ok(SeriesTabCursor::new()) + } +} + +/// A cursor for the Series virtual table +#[derive(Default)] +#[repr(C)] +struct SeriesTabCursor { + /// Base class. Must be first + base: ffi::sqlite3_vtab_cursor, + /// True to count down rather than up + is_desc: bool, + /// The rowid + row_id: i64, + /// Current value ("value") + value: i64, + /// Mimimum value ("start") + min_value: i64, + /// Maximum value ("stop") + max_value: i64, + /// Increment ("step") + step: i64, +} + +impl SeriesTabCursor { + fn new() -> SeriesTabCursor { + Default::default() + } +} +impl VTabCursor for SeriesTabCursor { + fn vtab(&self) -> &mut SeriesTab { + unsafe { &mut *(self.base.pVtab as *mut SeriesTab) } + } + fn filter(&mut self, + idx_num: libc::c_int, + _idx_str: Option<&str>, + args: &Values) + -> Result<()> { + let idx_num = QueryPlanFlags::from_bits_truncate(idx_num); + let mut i = 0; + if idx_num.contains(START) { + self.min_value = try!(args.get(i)); + i += 1; + } else { + self.min_value = 0; + } + if idx_num.contains(STOP) { + self.max_value = try!(args.get(i)); + i += 1; + } else { + self.max_value = 0xffffffff; + } + if idx_num.contains(STEP) { + self.step = try!(args.get(i)); + if self.step < 1 { + self.step = 1; + } + } else { + self.step = 1; + }; + self.is_desc = idx_num.contains(DESC); + if self.is_desc { + self.value = self.max_value; + if self.step > 1 { + self.value -= (self.max_value - self.min_value) % self.step; + } + } else { + self.value = self.min_value; + } + self.row_id = 0; + Ok(()) + } + fn next(&mut self) -> Result<()> { + if self.is_desc { + self.value -= self.step; + } else { + self.value += self.step; + } + self.row_id += 1; + Ok(()) + } + fn eof(&self) -> bool { + if self.is_desc { + self.value < self.min_value + } else { + self.value > self.max_value + } + } + fn column(&self, ctx: &mut Context, i: libc::c_int) -> Result<()> { + let x = match i { + SERIES_COLUMN_START => self.min_value, + SERIES_COLUMN_STOP => self.max_value, + SERIES_COLUMN_STEP => self.step, + _ => self.value, + }; + ctx.set_result(&x); + Ok(()) + } + fn rowid(&self) -> Result { + Ok(self.row_id) + } +}