diff --git a/Cargo.toml b/Cargo.toml index 6746bd8..254f323 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ sqlcipher = ["libsqlite3-sys/sqlcipher"] unlock_notify = ["libsqlite3-sys/unlock_notify"] vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"] csvtab = ["csv", "vtab"] +# pointer passing interfaces: 3.20.0 +array = ["vtab"] [dependencies] time = "0.1.0" diff --git a/src/context.rs b/src/context.rs index 33a7c15..1febd27 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,15 +1,18 @@ //! Code related to `sqlite3_context` common to `functions` and `vtab` modules. -use std::error::Error as StdError; use std::ffi::CStr; use std::os::raw::{c_char, c_int, c_void}; +#[cfg(feature = "array")] +use std::rc::Rc; use ffi; use ffi::sqlite3_context; use ffi::sqlite3_value; +use str_to_cstring; use types::{ToSqlOutput, ValueRef}; -use {str_to_cstring, Error}; +#[cfg(feature = "array")] +use vtab::array::{free_array, ARRAY_TYPE}; impl<'a> ValueRef<'a> { pub unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> { @@ -28,7 +31,8 @@ impl<'a> ValueRef<'a> { let s = CStr::from_ptr(text as *const c_char); // sqlite3_value_text returns UTF8 data, so our unwrap here should be fine. - let s = s.to_str() + let s = s + .to_str() .expect("sqlite3_value_text returned invalid UTF-8"); ValueRef::Text(s) } @@ -68,6 +72,15 @@ pub unsafe fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a> ToSqlOutput::ZeroBlob(len) => { return ffi::sqlite3_result_zeroblob(ctx, len); } + #[cfg(feature = "array")] + ToSqlOutput::Array(ref a) => { + return ffi::sqlite3_result_pointer( + ctx, + Rc::into_raw(a.clone()) as *mut c_void, + ARRAY_TYPE, + Some(free_array), + ); + } }; match value { @@ -109,33 +122,3 @@ pub unsafe fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a> } } } - -pub unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) { - // Extended constraint error codes were added in SQLite 3.7.16. We don't have an explicit - // feature check for that, and this doesn't really warrant one. We'll use the extended code - // if we're on the bundled version (since it's at least 3.17.0) and the normal constraint - // error code if not. - #[cfg(feature = "bundled")] - fn constraint_error_code() -> i32 { - ffi::SQLITE_CONSTRAINT_FUNCTION - } - #[cfg(not(feature = "bundled"))] - fn constraint_error_code() -> i32 { - ffi::SQLITE_CONSTRAINT - } - - match *err { - Error::SqliteFailure(ref err, ref s) => { - ffi::sqlite3_result_error_code(ctx, err.extended_code); - if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) { - ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); - } - } - _ => { - ffi::sqlite3_result_error_code(ctx, constraint_error_code()); - if let Ok(cstr) = str_to_cstring(err.description()) { - ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); - } - } - } -} diff --git a/src/functions.rs b/src/functions.rs index f3778dc..d3f1289 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -49,6 +49,7 @@ //! assert!(is_match); //! } //! ``` +use std::error::Error as StdError; use std::ptr; use std::slice; use std::os::raw::{c_int, c_void}; @@ -57,11 +58,41 @@ use ffi; use ffi::sqlite3_context; use ffi::sqlite3_value; -use context::{report_error, set_result}; +use context::{set_result}; use types::{ToSql, FromSql, FromSqlError, ValueRef}; use {Result, Error, Connection, str_to_cstring, InnerConnection}; +unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) { + // Extended constraint error codes were added in SQLite 3.7.16. We don't have an explicit + // feature check for that, and this doesn't really warrant one. We'll use the extended code + // if we're on the bundled version (since it's at least 3.17.0) and the normal constraint + // error code if not. + #[cfg(feature = "bundled")] + fn constraint_error_code() -> i32 { + ffi::SQLITE_CONSTRAINT_FUNCTION + } + #[cfg(not(feature = "bundled"))] + fn constraint_error_code() -> i32 { + ffi::SQLITE_CONSTRAINT + } + + match *err { + Error::SqliteFailure(ref err, ref s) => { + ffi::sqlite3_result_error_code(ctx, err.extended_code); + if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + } + _ => { + ffi::sqlite3_result_error_code(ctx, constraint_error_code()); + if let Ok(cstr) = str_to_cstring(err.description()) { + ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1); + } + } + } +} + unsafe extern "C" fn free_boxed_value(p: *mut c_void) { let _: Box = Box::from_raw(p as *mut T); } diff --git a/src/statement.rs b/src/statement.rs index c867ad4..06f037c 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -1,6 +1,8 @@ use std::{convert, fmt, mem, ptr, result, str}; use std::ffi::CStr; use std::os::raw::{c_char, c_int, c_void}; +#[cfg(feature = "array")] +use std::rc::Rc; use std::slice::from_raw_parts; use super::ffi; @@ -8,6 +10,8 @@ use super::{Connection, RawStatement, Result, Error, ValueRef, Row, Rows, AndThe use super::str_to_cstring; use types::{ToSql, ToSqlOutput}; use row::{RowsCrateImpl, MappedRowsCrateImpl, AndThenRowsCrateImpl}; +#[cfg(feature = "array")] +use vtab::array::{ARRAY_TYPE, free_array}; /// A prepared statement. pub struct Statement<'conn> { @@ -404,6 +408,11 @@ impl<'conn> Statement<'conn> { return self.conn .decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col, len) }); } + #[cfg(feature = "array")] + ToSqlOutput::Array(a) => { + return self.conn + .decode_result(unsafe { ffi::sqlite3_bind_pointer(ptr, col, Rc::into_raw(a) as *mut c_void, ARRAY_TYPE, Some(free_array)) }); + } }; self.conn .decode_result(match value { @@ -442,7 +451,7 @@ impl<'conn> Statement<'conn> { ffi::SQLITE_TRANSIENT()) } }, - }) + }) } fn execute_with_bound_parameters(&mut self) -> Result { diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs index ba8f5da..1053173 100644 --- a/src/types/to_sql.rs +++ b/src/types/to_sql.rs @@ -1,4 +1,6 @@ use super::{Null, Value, ValueRef}; +#[cfg(feature = "array")] +use vtab::array::Array; use Result; /// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait. @@ -13,6 +15,9 @@ pub enum ToSqlOutput<'a> { /// A BLOB of the given length that is filled with zeroes. #[cfg(feature = "blob")] ZeroBlob(i32), + + #[cfg(feature = "array")] + Array(Array) } // Generically allow any type that can be converted into a ValueRef @@ -59,6 +64,8 @@ impl<'a> ToSql for ToSqlOutput<'a> { #[cfg(feature = "blob")] ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i), + #[cfg(feature = "array")] + ToSqlOutput::Array(ref a) => ToSqlOutput::Array(a.clone()), }) } } diff --git a/src/vtab/array.rs b/src/vtab/array.rs new file mode 100644 index 0000000..0b763dc --- /dev/null +++ b/src/vtab/array.rs @@ -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); +} + +pub type Array = Rc>; + +impl ToSql for Array { + fn to_sql(&self) -> Result { + 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 { + 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 { + 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, +} + +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 { + Ok(self.row_id) + } +} diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index d16bc4b..2919995 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -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 { Ok(self.row_number as i64) @@ -365,7 +363,8 @@ mod test { assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers); } - let ids: Result> = s.query_map(&[], |row| row.get::(0)) + let ids: Result> = s + .query_map(&[], |row| row.get::(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(); diff --git a/src/vtab/int_array.rs b/src/vtab/int_array.rs index f786f6a..d18b0f7 100644 --- a/src/vtab/int_array.rs +++ b/src/vtab/int_array.rs @@ -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 { Ok(self.i as i64) diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index 9a139db..d988293 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -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(&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(&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> { + 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) + })) + } + } + 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 { - 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::>(); 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; diff --git a/src/vtab/series.rs b/src/vtab/series.rs index 8976a54..a469807 100644 --- a/src/vtab/series.rs +++ b/src/vtab/series.rs @@ -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 { Ok(self.row_id)