Draft for carray module in Rust

Incomplete support for sqlite3_bind_pointer.
Make Context::set_result return a Result.
Add Values::get_array.
This commit is contained in:
gwenn
2018-06-10 12:16:54 +02:00
parent b89b574f81
commit fa64a4d0bf
10 changed files with 290 additions and 60 deletions

179
src/vtab/array.rs Normal file
View File

@@ -0,0 +1,179 @@
//! Array Virtual Table
//! Port of [carray](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/carray.c) C extension.
use std::default::Default;
use std::os::raw::{c_char, c_int, c_void};
use std::rc::Rc;
use ffi;
use types::{ToSql, ToSqlOutput, Value};
use vtab::{self, declare_vtab, Context, IndexInfo, VTab, VTabCursor, Values};
use {Connection, Error, Result};
// http://sqlite.org/bindptr.html
pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
let _: Array = Rc::from_raw(p as *const Vec<Value>);
}
pub type Array = Rc<Vec<Value>>;
impl ToSql for Array {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(ToSqlOutput::Array(self.clone()))
}
}
/// Register the "rarray" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("rarray", &ARRAY_MODULE, aux)
}
eponymous_module!(
ARRAY_MODULE,
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
);
// Column numbers
// const CARRAY_COLUMN_VALUE : c_int = 0;
const CARRAY_COLUMN_POINTER: c_int = 1;
/// An instance of the Array virtual table
#[repr(C)]
struct ArrayTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
}
impl VTab for ArrayTab {
type Aux = ();
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<()> {
// Index of the pointer= constraint
let mut ptr_idx = None;
for (i, constraint) in info.constraints().enumerate() {
if !constraint.is_usable() {
continue;
}
if constraint.operator() != vtab::IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
continue;
}
match constraint.column() {
CARRAY_COLUMN_POINTER => {
ptr_idx = Some(i);
}
_ => {}
}
}
if let Some(ptr_idx) = ptr_idx {
{
let mut constraint_usage = info.constraint_usage(ptr_idx);
constraint_usage.set_argv_index(1);
constraint_usage.set_omit(true);
}
info.set_estimated_cost(1f64);
info.set_estimated_rows(100);
info.set_idx_num(1);
} else {
info.set_estimated_cost(2_147_483_647f64);
info.set_estimated_rows(2_147_483_647);
info.set_idx_num(0);
}
Ok(())
}
fn open(&self) -> Result<ArrayTabCursor> {
Ok(ArrayTabCursor::new())
}
}
/// A cursor for the Array virtual table
#[repr(C)]
struct ArrayTabCursor {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
/// Pointer to the array of values ("pointer")
ptr: Option<Array>,
}
impl ArrayTabCursor {
fn new() -> ArrayTabCursor {
ArrayTabCursor {
base: Default::default(),
row_id: 0,
ptr: None,
}
}
fn len(&self) -> i64 {
match self.ptr {
Some(ref a) => a.len() as i64,
_ => 0,
}
}
}
impl VTabCursor for ArrayTabCursor {
type Table = ArrayTab;
fn vtab(&self) -> &ArrayTab {
unsafe { &*(self.base.pVtab as *const ArrayTab) }
}
fn filter(&mut self, idx_num: c_int, _idx_str: Option<&str>, args: &Values) -> Result<()> {
if idx_num > 0 {
self.ptr = try!(args.get_array(0));
} else {
self.ptr = None;
}
self.row_id = 1;
Ok(())
}
fn next(&mut self) -> Result<()> {
self.row_id += 1;
Ok(())
}
fn eof(&self) -> bool {
self.row_id > self.len()
}
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
match i {
CARRAY_COLUMN_POINTER => Ok(()),
_ => {
if let Some(ref array) = self.ptr {
let value = &array[i as usize];
ctx.set_result(&value)
} else {
Ok(())
}
}
}
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}

View File

@@ -327,12 +327,10 @@ impl VTabCursor for CSVTabCursor {
)));
}
if self.cols.is_empty() {
ctx.set_result(&Null);
return Ok(());
return ctx.set_result(&Null);
}
// TODO Affinity
ctx.set_result(&self.cols[col as usize].to_owned());
Ok(())
ctx.set_result(&self.cols[col as usize].to_owned())
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_number as i64)
@@ -365,7 +363,8 @@ mod test {
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
}
let ids: Result<Vec<i32>> = s.query_map(&[], |row| row.get::<i32, i32>(0))
let ids: Result<Vec<i32>> = s
.query_map(&[], |row| row.get::<i32, i32>(0))
.unwrap()
.collect();
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id);
@@ -382,10 +381,11 @@ mod test {
.unwrap();
{
let mut s = db.prepare(
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
v1.rowid < v2.rowid",
).unwrap();
let mut s =
db.prepare(
"SELECT v1.rowid, v1.* FROM vtab v1 NATURAL JOIN vtab v2 WHERE \
v1.rowid < v2.rowid",
).unwrap();
let mut rows = s.query(&[]).unwrap();
let row = rows.next().unwrap().unwrap();

View File

@@ -139,9 +139,8 @@ impl VTabCursor for IntArrayVTabCursor {
let vtab = self.vtab();
unsafe {
let array = (*vtab.array).borrow();
ctx.set_result(&array[self.i]);
ctx.set_result(&array[self.i])
}
Ok(())
}
fn rowid(&self) -> Result<i64> {
Ok(self.i as i64)

View File

@@ -6,7 +6,7 @@ use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::slice;
use context::{report_error, set_result};
use context::set_result;
use error::error_from_sqlite_code;
use ffi;
use types::{FromSql, FromSqlError, ToSql, ValueRef};
@@ -207,12 +207,10 @@ pub trait VTabCursor: Sized {
pub struct Context(*mut ffi::sqlite3_context);
impl Context {
pub fn set_result<T: ToSql>(&mut self, value: &T) {
let t = value.to_sql();
match t {
Ok(ref value) => unsafe { set_result(self.0, value) },
Err(err) => unsafe { report_error(self.0, &err) },
}
pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
let t = value.to_sql()?;
unsafe { set_result(self.0, &t) };
Ok(())
}
}
@@ -241,6 +239,22 @@ impl<'a> Values<'a> {
})
}
// `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
// So it seems not possible to enhance `ValueRef::from_value`.
#[cfg(feature = "array")]
pub fn get_array(&self, idx: usize) -> Result<Option<array::Array>> {
use types::Value;
let arg = self.args[idx];
let ptr = unsafe { ffi::sqlite3_value_pointer(arg, array::ARRAY_TYPE) };
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(unsafe {
array::Array::from_raw(ptr as *const Vec<Value>)
}))
}
}
pub fn iter(&self) -> ValueIter {
ValueIter {
iter: self.args.iter(),
@@ -361,12 +375,16 @@ pub fn dequote(s: &str) -> &str {
/// 0 no false off
/// ```
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)
} 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)
} else {
@@ -530,7 +548,8 @@ macro_rules! create_or_connect {
let aux = aux as *mut $aux;
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args.iter()
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes())
.collect::<Vec<_>>();
match $vtab::$vtab_func(db, aux, &vec[..]) {
@@ -756,6 +775,8 @@ pub fn mprintf(err_msg: &str) -> *mut c_char {
unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) }
}
#[cfg(feature = "array")]
pub mod array;
#[cfg(feature = "csvtab")]
pub mod csvtab;
pub mod int_array;

View File

@@ -255,8 +255,7 @@ impl VTabCursor for SeriesTabCursor {
SERIES_COLUMN_STEP => self.step,
_ => self.value,
};
ctx.set_result(&x);
Ok(())
ctx.set_result(&x)
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)