Add series table-valued-function.

This commit is contained in:
gwenn 2016-08-14 19:53:47 +02:00
parent 3b5bd7abab
commit 424a6c0cc8
4 changed files with 270 additions and 19 deletions

View File

@ -20,9 +20,11 @@ pub fn load_module(conn: &Connection) -> Result<()> {
init_module!(CSV_MODULE, init_module!(CSV_MODULE,
CSVTab, CSVTab,
CSVTabCursor, CSVTabCursor,
csv_create, Some(csv_connect),
csv_connect,
csv_best_index, csv_best_index,
csv_destroy, csv_disconnect,
Some(csv_disconnect),
csv_open, csv_open,
csv_close, csv_close,
csv_filter, csv_filter,
@ -56,7 +58,7 @@ impl CSVTab {
} }
impl VTab<CSVTabCursor> for CSVTab { impl VTab<CSVTabCursor> for CSVTab {
fn create(db: *mut ffi::sqlite3, _aux: *mut libc::c_void, args: &[&[u8]]) -> Result<CSVTab> { fn connect(db: *mut ffi::sqlite3, _aux: *mut libc::c_void, 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()));
} }

View File

@ -1,4 +1,5 @@
//! Int array virtual table. //! 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::cell::RefCell;
use std::default::Default; use std::default::Default;
use std::mem; use std::mem;
@ -39,9 +40,11 @@ pub fn drop_int_array(conn: &Connection, name: &str) -> Result<()> {
init_module!(INT_ARRAY_MODULE, init_module!(INT_ARRAY_MODULE,
IntArrayVTab, IntArrayVTab,
IntArrayVTabCursor, IntArrayVTabCursor,
int_array_create, Some(int_array_connect),
int_array_connect,
int_array_best_index, int_array_best_index,
int_array_destroy, int_array_disconnect,
Some(int_array_disconnect),
int_array_open, int_array_open,
int_array_close, int_array_close,
int_array_filter, int_array_filter,
@ -58,7 +61,7 @@ struct IntArrayVTab {
} }
impl VTab<IntArrayVTabCursor> for IntArrayVTab { impl VTab<IntArrayVTabCursor> for IntArrayVTab {
fn create(db: *mut ffi::sqlite3, fn connect(db: *mut ffi::sqlite3,
aux: *mut libc::c_void, aux: *mut libc::c_void,
_args: &[&[u8]]) _args: &[&[u8]])
-> Result<IntArrayVTab> { -> Result<IntArrayVTab> {

View File

@ -44,7 +44,7 @@ use types::FromSql;
pub trait VTab<C: VTabCursor<Self>>: Sized { pub trait VTab<C: VTabCursor<Self>>: Sized {
/// 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 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<Self>; fn connect(db: *mut ffi::sqlite3, aux: *mut libc::c_void, args: &[&[u8]]) -> Result<Self>;
/// 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.
@ -90,7 +90,7 @@ impl IndexInfo {
} }
} }
/// True if this constraint is usable /// 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; use std::slice;
unsafe { unsafe {
let constraints = slice::from_raw_parts((*self.0).aConstraint, let constraints = slice::from_raw_parts((*self.0).aConstraint,
@ -288,18 +288,19 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut libc::c_void) {
#[macro_export] #[macro_export]
macro_rules! init_module { macro_rules! init_module {
($module_name: ident, $vtab: ident, $cursor: ty, ($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, $open: ident, $close: ident,
$filter: ident, $next: ident, $eof: ident, $filter: ident, $next: ident, $eof: ident,
$column: ident, $rowid: 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: $create,
xConnect: Some($create), /* A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method */ 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), xBestIndex: Some($best_index),
xDisconnect: Some($destroy), xDisconnect: Some($disconnect),
xDestroy: Some($destroy), xDestroy: $destroy,
xOpen: Some($open), xOpen: Some($open),
xClose: Some($close), xClose: Some($close),
xFilter: Some($filter), xFilter: Some($filter),
@ -319,7 +320,7 @@ static $module_name: ffi::sqlite3_module = ffi::sqlite3_module {
xRollbackTo: None, 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, aux: *mut libc::c_void,
argc: libc::c_int, argc: libc::c_int,
argv: *const *const libc::c_char, 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| { let vec = args.iter().map(|cs| {
CStr::from_ptr(*cs).to_bytes() CStr::from_ptr(*cs).to_bytes()
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
match $vtab::create(db, aux, &vec[..]) { match $vtab::connect(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;
@ -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 vtab = vtab as *mut $vtab;
let _: Box<$vtab> = Box::from_raw(vtab); let _: Box<$vtab> = Box::from_raw(vtab);
ffi::SQLITE_OK ffi::SQLITE_OK
@ -536,3 +537,4 @@ pub fn mprintf(err_msg: &str) -> *mut ::libc::c_char {
pub mod int_array; pub mod int_array;
#[cfg(feature = "csvtab")] #[cfg(feature = "csvtab")]
pub mod csvtab; pub mod csvtab;
pub mod series;

244
src/vtab/series.rs Normal file
View File

@ -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<SeriesTabCursor> for SeriesTab {
fn connect(db: *mut ffi::sqlite3,
_aux: *mut libc::c_void,
_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<()> {
// 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<SeriesTabCursor> {
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<SeriesTab> 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<i64> {
Ok(self.row_id)
}
}