From e17678b17308e1dae24a97e7a54cf83be812e676 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 24 Jan 2016 12:18:21 +0100 Subject: [PATCH] IntArray virtual table. --- src/lib.rs | 2 +- src/vtab.rs | 239 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 174 insertions(+), 67 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2b1ea15..90034fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ mod error; #[cfg(feature = "backup")]pub mod backup; #[cfg(feature = "functions")] pub mod functions; #[cfg(feature = "blob")] pub mod blob; -#[cfg(feature = "vtab")] pub mod vtab; +#[cfg(all(feature = "vtab", feature = "functions"))] pub mod vtab; /// Old name for `Result`. `SqliteResult` is deprecated. pub type SqliteResult = Result; diff --git a/src/vtab.rs b/src/vtab.rs index 7316801..d35e11e 100644 --- a/src/vtab.rs +++ b/src/vtab.rs @@ -1,15 +1,17 @@ //! Create virtual tables. //! (See http://sqlite.org/vtab.html) +use std::cell::RefCell; use std::default::Default; use std::error::Error as StdError; use std::ffi::CString; use std::mem; +use std::rc::Rc; use libc; use {Connection, Error, Result, InnerConnection, str_to_cstring}; use error::error_from_sqlite_code; use ffi; -use functions::{ToResult, NoMem, TooBig}; +use functions::ToResult; // let conn: Connection = ...; // let mod: Module = ...; // VTab builder @@ -52,12 +54,52 @@ impl Connection { } } +impl InnerConnection { + fn create_module(&mut self, + module_name: &str, + module: *const ffi::sqlite3_module, + aux: A) + -> Result<()> { + let boxed_aux: *mut A = Box::into_raw(Box::new(aux)); + let c_name = try!(str_to_cstring(module_name)); + let r = unsafe { + ffi::sqlite3_create_module_v2(self.db(), + c_name.as_ptr(), + module, + mem::transmute(boxed_aux), + Some(mem::transmute(free_boxed_value::))) + }; + self.decode_result(r) + } +} + // FIXME copy/paste from function.rs unsafe extern "C" fn free_boxed_value(p: *mut libc::c_void) { let _: Box = Box::from_raw(mem::transmute(p)); } -static RUST_MODULE: ffi::sqlite3_module = ffi::sqlite3_module { +pub fn create_int_array(conn: &Connection, name: &str) -> Result>>> { + let array = Rc::new(RefCell::new(Vec::new())); + try!(conn.create_module(name, &INT_ARRAY_MODULE, array.clone())); + try!(conn.execute_batch(&format!("CREATE VIRTUAL TABLE temp.{0} USING {0}", + escape_quote(name.to_string())))); + Ok(array) +} + +pub fn drop_int_array(conn: &Connection, name: &str) -> Result<()> { + conn.execute_batch(&format!("DROP TABLE temp.{0}", escape_quote(name.to_string()))) +} + +fn escape_quote(identifier: String) -> String { + if identifier.contains('"') { + // escape quote by doubling them + identifier.replace('"', "\"\"") + } else { + identifier + } +} + +static INT_ARRAY_MODULE: ffi::sqlite3_module = ffi::sqlite3_module { iVersion: 1, xCreate: Some(x_create), xConnect: Some(x_create), /* A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method */ @@ -90,9 +132,9 @@ unsafe extern "C" fn x_create(db: *mut ffi::sqlite3, pp_vtab: *mut *mut ffi::sqlite3_vtab, err_msg: *mut *mut libc::c_char) -> libc::c_int { - match VTab::new(db, aux, argc, argv) { + match IntArrayVTab::create(db, aux, argc, argv) { Ok(vtab) => { - let boxed_vtab: *mut VTab = Box::into_raw(Box::new(vtab)); + let boxed_vtab: *mut IntArrayVTab = Box::into_raw(Box::new(vtab)); *pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab; ffi::SQLITE_OK } @@ -114,31 +156,35 @@ unsafe extern "C" fn x_create(db: *mut ffi::sqlite3, } unsafe extern "C" fn x_destroy(vtab: *mut ffi::sqlite3_vtab) -> libc::c_int { - let vtab = vtab as *mut VTab; - let _: Box = Box::from_raw(mem::transmute(vtab)); + let vtab = vtab as *mut IntArrayVTab; + let _: Box = Box::from_raw(mem::transmute(vtab)); ffi::SQLITE_OK } #[repr(C)] -struct VTab { +struct IntArrayVTab { /// Base class base: ffi::sqlite3_vtab, + array: *const Rc>>, } -impl VTab { - fn new(db: *mut ffi::sqlite3, - aux: *mut libc::c_void, - argc: libc::c_int, - argv: *const *const libc::c_char) - -> Result { - unimplemented!() +impl IntArrayVTab { + fn create(db: *mut ffi::sqlite3, + aux: *mut libc::c_void, + _argc: libc::c_int, + _argv: *const *const libc::c_char) + -> Result { + let array = unsafe { mem::transmute(aux) }; + let vtab = IntArrayVTab { + base: Default::default(), + array: array, + }; + try!(IntArrayVTab::declare_vtab(db, "CREATE TABLE x(value INTEGER PRIMARY KEY)")); + Ok(vtab) } - fn open(&self) -> VTabCursor { - VTabCursor::new() - } - unsafe fn declare_vtab(db: *mut ffi::sqlite3, sql: &str) -> Result<()> { + fn declare_vtab(db: *mut ffi::sqlite3, sql: &str) -> Result<()> { let c_sql = try!(CString::new(sql)); - let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()); + let rc = unsafe { ffi::sqlite3_declare_vtab(db, c_sql.as_ptr()) }; if rc == ffi::SQLITE_OK { Ok(()) } else { @@ -146,54 +192,65 @@ impl VTab { } } - unsafe fn set_err_msg(&mut self, err_msg: &str) -> libc::c_int { + fn best_index(&self, _info: *mut ffi::sqlite3_index_info) { + // unimplemented!() + } + + fn open(&self) -> IntArrayVTabCursor { + IntArrayVTabCursor::new() + } + + fn set_err_msg(&mut self, err_msg: &str) { if !self.base.zErrMsg.is_null() { - ffi::sqlite3_free(self.base.zErrMsg as *mut libc::c_void) + unsafe { + ffi::sqlite3_free(self.base.zErrMsg as *mut libc::c_void); + } } self.base.zErrMsg = mprintf(err_msg); - return ffi::SQLITE_ERROR; } } -unsafe extern "C" fn x_best_index(_vtab: *mut ffi::sqlite3_vtab, - _info: *mut ffi::sqlite3_index_info) +unsafe extern "C" fn x_best_index(vtab: *mut ffi::sqlite3_vtab, + info: *mut ffi::sqlite3_index_info) -> libc::c_int { + let vtab = vtab as *mut IntArrayVTab; + (*vtab).best_index(info); ffi::SQLITE_OK } unsafe extern "C" fn x_open(vtab: *mut ffi::sqlite3_vtab, pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor) -> libc::c_int { - let vtab = vtab as *mut VTab; + let vtab = vtab as *mut IntArrayVTab; let cursor = (*vtab).open(); - let boxed_cursor: *mut VTabCursor = Box::into_raw(Box::new(cursor)); + let boxed_cursor: *mut IntArrayVTabCursor = Box::into_raw(Box::new(cursor)); *pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor; ffi::SQLITE_OK } unsafe extern "C" fn x_close(cursor: *mut ffi::sqlite3_vtab_cursor) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; - let _: Box = Box::from_raw(mem::transmute(cursor)); + let cursor = cursor as *mut IntArrayVTabCursor; + let _: Box = Box::from_raw(mem::transmute(cursor)); ffi::SQLITE_OK } #[repr(C)] -struct VTabCursor { +struct IntArrayVTabCursor { /// Base class base: ffi::sqlite3_vtab_cursor, /// Current cursor position i: usize, } -impl VTabCursor { - fn new() -> VTabCursor { - VTabCursor { +impl IntArrayVTabCursor { + fn new() -> IntArrayVTabCursor { + IntArrayVTabCursor { base: Default::default(), i: 0, } } - unsafe fn vtab(&self) -> *mut VTab { - self.base.pVtab as *mut VTab + fn vtab(&self) -> *mut IntArrayVTab { + self.base.pVtab as *mut IntArrayVTab } fn filter(&mut self) -> libc::c_int { @@ -205,11 +262,19 @@ impl VTabCursor { ffi::SQLITE_OK } fn eof(&self) -> bool { - unimplemented!() + let vtab = self.vtab(); + unsafe { + let array = (*(*vtab).array).borrow(); + self.i >= array.len() + } } - fn column(&self, ctx: *mut ffi::sqlite3_context, i: libc::c_int) -> libc::c_int { - // FIXME Result<()> - unimplemented!() + fn column(&self, ctx: *mut ffi::sqlite3_context, _i: libc::c_int) -> libc::c_int { + let vtab = self.vtab(); + unsafe { + let array = (*(*vtab).array).borrow(); + array[self.i].set_result(ctx); + } + ffi::SQLITE_OK } fn rowid(&self) -> i64 { self.i as i64 @@ -222,33 +287,33 @@ unsafe extern "C" fn x_filter(cursor: *mut ffi::sqlite3_vtab_cursor, _argc: libc::c_int, _argv: *mut *mut ffi::sqlite3_value) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; + let cursor = cursor as *mut IntArrayVTabCursor; (*cursor).filter() } unsafe extern "C" fn x_next(cursor: *mut ffi::sqlite3_vtab_cursor) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; + let cursor = cursor as *mut IntArrayVTabCursor; (*cursor).next() } unsafe extern "C" fn x_eof(cursor: *mut ffi::sqlite3_vtab_cursor) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; + let cursor = cursor as *mut IntArrayVTabCursor; (*cursor).eof() as libc::c_int } unsafe extern "C" fn x_column(cursor: *mut ffi::sqlite3_vtab_cursor, ctx: *mut ffi::sqlite3_context, i: libc::c_int) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; + let cursor = cursor as *mut IntArrayVTabCursor; (*cursor).column(ctx, i) } unsafe extern "C" fn x_rowid(cursor: *mut ffi::sqlite3_vtab_cursor, p_rowid: *mut ffi::sqlite3_int64) -> libc::c_int { - let cursor = cursor as *mut VTabCursor; + let cursor = cursor as *mut IntArrayVTabCursor; *p_rowid = (*cursor).rowid(); ffi::SQLITE_OK } -unsafe extern "C" fn result_error(ctx: *mut ffi::sqlite3_context, err: Error) -> libc::c_int { +unsafe fn result_error(ctx: *mut ffi::sqlite3_context, err: Error) -> libc::c_int { match err { Error::SqliteFailure(err, s) => { ffi::sqlite3_result_error_code(ctx, err.extended_code); @@ -268,33 +333,75 @@ unsafe extern "C" fn result_error(ctx: *mut ffi::sqlite3_context, err: Error) -> } // Space to hold this error message string must be obtained from an SQLite memory allocation function. -unsafe fn mprintf(err_msg: &str) -> *mut ::libc::c_char { +fn mprintf(err_msg: &str) -> *mut ::libc::c_char { let c_format = CString::new("%s").unwrap(); let c_err = CString::new(err_msg).unwrap(); - ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) -} - -impl InnerConnection { - fn create_module(&mut self, - module_name: &str, - module: *const ffi::sqlite3_module, - aux: A) - -> Result<()> { - // FIXME Both rust_module and aux need to be boxed - let boxed_aux: *mut A = Box::into_raw(Box::new(aux)); - let c_name = try!(str_to_cstring(module_name)); - let r = unsafe { - ffi::sqlite3_create_module_v2(self.db(), - c_name.as_ptr(), - module, - mem::transmute(boxed_aux), - Some(mem::transmute(free_boxed_value::))) - }; - self.decode_result(r) - } + unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) } } #[cfg(test)] mod test { + use Connection; + use vtab; + #[test] + #[cfg_attr(rustfmt, rustfmt_skip)] + fn test_int_array_module() { + let db = Connection::open_in_memory().unwrap(); + db.execute_batch("CREATE TABLE t1 (x INT); + INSERT INTO t1 VALUES (1), (3); + CREATE TABLE t2 (y INT); + INSERT INTO t2 VALUES (11); + CREATE TABLE t3 (z INT); + INSERT INTO t3 VALUES (-5);").unwrap(); + let p1 = vtab::create_int_array(&db, "ex1").unwrap(); + let p2 = vtab::create_int_array(&db, "ex2").unwrap(); + let p3 = vtab::create_int_array(&db, "ex3").unwrap(); + + let mut s = db.prepare("SELECT * FROM t1, t2, t3 + WHERE t1.x IN ex1 + AND t2.y IN ex2 + AND t3.z IN ex3").unwrap(); + + p1.borrow_mut().append(&mut vec![1, 2, 3, 4]); + p2.borrow_mut().append(&mut vec![5, 6, 7, 8, 9, 10, 11]); + p3.borrow_mut().append(&mut vec![-1, -5, -10]); + + { + let rows = s.query(&[]).unwrap(); + for row in rows { + let row = row.unwrap(); + let i1: i64 = row.get(0); + assert!(i1 == 1 || i1 == 3); + assert_eq!(11, row.get(1)); + assert_eq!(-5, row.get(2)); + } + } + + s.reset_if_needed(); + p1.borrow_mut().clear(); + p2.borrow_mut().clear(); + p3.borrow_mut().clear(); + p1.borrow_mut().append(&mut vec![1]); + p2.borrow_mut().append(&mut vec![7, 11]); + p3.borrow_mut().append(&mut vec![-5, -10]); + + { + let row = s.query(&[]).unwrap().next().unwrap().unwrap(); + assert_eq!(1, row.get(0)); + assert_eq!(11, row.get(1)); + assert_eq!(-5, row.get(2)); + } + + s.reset_if_needed(); + p2.borrow_mut().clear(); + p3.borrow_mut().clear(); + p2.borrow_mut().append(&mut vec![3, 4, 5]); + p3.borrow_mut().append(&mut vec![0, -5]); + assert!(s.query(&[]).unwrap().next().is_none()); + + vtab::drop_int_array(&db, "ex1").unwrap(); + vtab::drop_int_array(&db, "ex2").unwrap(); + vtab::drop_int_array(&db, "ex3").unwrap(); + } }