Merge pull request #130 from gwenn/vtab

Virtual Table
This commit is contained in:
gwenn 2018-08-11 13:58:41 +02:00 committed by GitHub
commit 8c0183482f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 4324 additions and 120 deletions

View File

@ -1,5 +1,4 @@
sudo: false
dist: trusty
language: rust
@ -39,7 +38,8 @@ script:
- cargo test --features bundled
- cargo test --features sqlcipher
- cargo test --features "unlock_notify bundled"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled"
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen"
- cargo test --features "array csvtab vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"

View File

@ -29,6 +29,10 @@ limits = []
hooks = []
sqlcipher = ["libsqlite3-sys/sqlcipher"]
unlock_notify = ["libsqlite3-sys/unlock_notify"]
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7", "lazy_static"]
csvtab = ["csv", "vtab"]
# pointer passing interfaces: 3.20.0
array = ["vtab", "bundled"]
[dependencies]
time = "0.1.0"
@ -36,6 +40,8 @@ bitflags = "1.0"
lru-cache = "0.1"
chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true }
[dev-dependencies]
tempdir = "0.3"
@ -53,8 +59,11 @@ harness = false
[[test]]
name = "deny_single_threaded_sqlite_config"
[[test]]
name = "vtab"
[package.metadata.docs.rs]
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace" ]
features = [ "backup", "blob", "chrono", "functions", "limits", "load_extension", "serde_json", "trace", "vtab" ]
all-features = false
no-default-features = true
default-target = "x86_64-unknown-linux-gnu"

View File

@ -34,10 +34,10 @@ build: false
test_script:
- cargo test --lib --verbose
- cargo test --lib --verbose --features bundled
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled"
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
cache:
- C:\Users\appveyor\.cargo

View File

@ -20,6 +20,7 @@ min_sqlite_version_3_6_11 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_6_23 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_3 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
# sqlite3_unlock_notify >= 3.6.12
unlock_notify = []

File diff suppressed because it is too large Load Diff

View File

@ -172,19 +172,16 @@ mod build {
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
"bindgen-bindings/bindgen_3.6.8.rs",
#[cfg(feature = "min_sqlite_version_3_6_11")]
"bindgen-bindings/bindgen_3.6.11.rs",
#[cfg(feature = "min_sqlite_version_3_6_23")]
"bindgen-bindings/bindgen_3.6.23.rs",
#[cfg(feature = "min_sqlite_version_3_7_3")]
"bindgen-bindings/bindgen_3.7.3.rs",
#[cfg(feature = "min_sqlite_version_3_7_4")]
"bindgen-bindings/bindgen_3.7.4.rs",
#[cfg(feature = "min_sqlite_version_3_7_7")]
"bindgen-bindings/bindgen_3.7.7.rs",
#[cfg(feature = "min_sqlite_version_3_7_16")]
"bindgen-bindings/bindgen_3.7.16.rs",
];

View File

@ -2,6 +2,7 @@
pub use self::error::*;
use std::default::Default;
use std::mem;
mod error;
@ -45,3 +46,18 @@ pub enum Limit {
}
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
pub type sqlite3_index_constraint = sqlite3_index_info_sqlite3_index_constraint;
pub type sqlite3_index_constraint_usage = sqlite3_index_info_sqlite3_index_constraint_usage;
impl Default for sqlite3_vtab {
fn default() -> Self {
unsafe { mem::zeroed() }
}
}
impl Default for sqlite3_vtab_cursor {
fn default() -> Self {
unsafe { mem::zeroed() }
}
}

124
src/context.rs Normal file
View File

@ -0,0 +1,124 @@
//! Code related to `sqlite3_context` common to `functions` and `vtab` modules.
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};
#[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE};
impl<'a> ValueRef<'a> {
pub(crate) unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> {
use std::slice::from_raw_parts;
match ffi::sqlite3_value_type(value) {
ffi::SQLITE_NULL => ValueRef::Null,
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
ffi::SQLITE_TEXT => {
let text = ffi::sqlite3_value_text(value);
assert!(
!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
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()
.expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
let (blob, len) = (
ffi::sqlite3_value_blob(value),
ffi::sqlite3_value_bytes(value),
);
assert!(
len >= 0,
"unexpected negative return from sqlite3_value_bytes"
);
if len > 0 {
assert!(
!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
ValueRef::Blob(&[])
}
}
_ => unreachable!("sqlite3_value_type returned invalid value"),
}
}
}
pub(crate) unsafe fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
let value = match *result {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
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 {
ValueRef::Null => ffi::sqlite3_result_null(ctx),
ValueRef::Integer(i) => ffi::sqlite3_result_int64(ctx, i),
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
ValueRef::Text(s) => {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
}
}
ValueRef::Blob(b) => {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0)
} else {
ffi::sqlite3_result_blob(
ctx,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT(),
);
}
}
}
}

View File

@ -68,6 +68,10 @@ pub enum Error {
/// to the requested type.
#[cfg(feature = "functions")]
InvalidFunctionParameterType(usize, Type),
/// Error returned by `vtab::Values::get` when the filter argument cannot be converted
/// to the requested type.
#[cfg(feature = "vtab")]
InvalidFilterParameterType(usize, Type),
/// An error case available for implementors of custom user functions (e.g.,
/// `create_scalar_function`).
@ -80,6 +84,12 @@ pub enum Error {
/// Error when the SQL is not a `SELECT`, is not read-only.
InvalidQuery,
/// An error case available for implementors of custom modules (e.g.,
/// `create_module`).
#[cfg(feature = "vtab")]
#[allow(dead_code)]
ModuleError(String),
}
impl From<str::Utf8Error> for Error {
@ -130,10 +140,16 @@ impl fmt::Display for Error {
Error::InvalidFunctionParameterType(i, ref t) => {
write!(f, "Invalid function parameter type {} at index {}", t, i)
}
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(i, ref t) => {
write!(f, "Invalid filter parameter type {} at index {}", t, i)
}
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => err.fmt(f),
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
Error::InvalidQuery => write!(f, "Query is not read-only"),
#[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => write!(f, "{}", desc),
}
}
}
@ -163,10 +179,14 @@ impl error::Error for Error {
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => err.description(),
Error::ToSqlConversionFailure(ref err) => err.description(),
Error::InvalidQuery => "query is not read-only",
#[cfg(feature = "vtab")]
Error::ModuleError(ref desc) => desc,
}
}
@ -190,12 +210,17 @@ impl error::Error for Error {
#[cfg(feature = "functions")]
Error::InvalidFunctionParameterType(_, _) => None,
#[cfg(feature = "vtab")]
Error::InvalidFilterParameterType(_, _) => None,
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err),
Error::FromSqlConversionFailure(_, _, ref err)
| Error::ToSqlConversionFailure(ref err) => Some(&**err),
#[cfg(feature = "vtab")]
Error::ModuleError(_) => None,
}
}
}

View File

@ -50,8 +50,7 @@
//! }
//! ```
use std::error::Error as StdError;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void};
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::slice;
@ -59,61 +58,11 @@ use ffi;
use ffi::sqlite3_context;
use ffi::sqlite3_value;
use types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef};
use context::set_result;
use types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result};
fn set_result<'a>(ctx: *mut sqlite3_context, result: &ToSqlOutput<'a>) {
let value = match *result {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
#[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => {
return unsafe { ffi::sqlite3_result_zeroblob(ctx, len) };
}
};
match value {
ValueRef::Null => unsafe { ffi::sqlite3_result_null(ctx) },
ValueRef::Integer(i) => unsafe { ffi::sqlite3_result_int64(ctx, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_result_double(ctx, r) },
ValueRef::Text(s) => unsafe {
let length = s.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else {
let c_str = match str_to_cstring(s) {
Ok(c_str) => c_str,
// TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
};
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
}
},
ValueRef::Blob(b) => unsafe {
let length = b.len();
if length > ::std::i32::MAX as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0)
} else {
ffi::sqlite3_result_blob(
ctx,
b.as_ptr() as *const c_void,
length as c_int,
ffi::SQLITE_TRANSIENT(),
);
}
},
}
}
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
@ -144,55 +93,6 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
}
}
impl<'a> ValueRef<'a> {
unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> {
use std::slice::from_raw_parts;
match ffi::sqlite3_value_type(value) {
ffi::SQLITE_NULL => ValueRef::Null,
ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
ffi::SQLITE_TEXT => {
let text = ffi::sqlite3_value_text(value);
assert!(
!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
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()
.expect("sqlite3_value_text returned invalid UTF-8");
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
let (blob, len) = (
ffi::sqlite3_value_blob(value),
ffi::sqlite3_value_bytes(value),
);
assert!(
len >= 0,
"unexpected negative return from sqlite3_value_bytes"
);
if len > 0 {
assert!(
!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
ValueRef::Blob(&[])
}
}
_ => unreachable!("sqlite3_value_type returned invalid value"),
}
}
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
drop(Box::from_raw(p as *mut T));
}
@ -277,7 +177,7 @@ where
{
/// Initializes the aggregation context. Will be called prior to the first call
/// to `step()` to set up the context for an invocation of the function. (Note:
/// `init()` will not be called if the there are no rows.)
/// `init()` will not be called if there are no rows.)
fn init(&self) -> A;
/// "step" function called once for each row in an aggregate group. May be called

View File

@ -56,7 +56,7 @@ extern crate libsqlite3_sys as ffi;
extern crate lru_cache;
#[macro_use]
extern crate bitflags;
#[cfg(test)]
#[cfg(any(test, feature = "vtab"))]
#[macro_use]
extern crate lazy_static;
@ -108,6 +108,8 @@ pub mod backup;
pub mod blob;
mod busy;
mod cache;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
mod error;
#[cfg(feature = "functions")]
pub mod functions;
@ -126,6 +128,8 @@ mod transaction;
pub mod types;
mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
pub mod vtab;
// Number of cached prepared statements we'll hold on to.
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;

View File

@ -1,5 +1,7 @@
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 std::{convert, fmt, mem, ptr, result, str};
@ -9,6 +11,8 @@ use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
};
use types::{ToSql, ToSqlOutput};
#[cfg(feature = "array")]
use vtab::array::{free_array, ARRAY_TYPE};
/// A prepared statement.
pub struct Statement<'conn> {
@ -419,6 +423,18 @@ impl<'conn> Statement<'conn> {
.conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
}
#[cfg(feature = "array")]
ToSqlOutput::Array(a) => {
return self.conn.decode_result(unsafe {
ffi::sqlite3_bind_pointer(
ptr,
col as c_int,
Rc::into_raw(a) as *mut c_void,
ARRAY_TYPE,
Some(free_array),
)
});
}
};
self.conn.decode_result(match value {
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },

View File

@ -1,5 +1,7 @@
use super::{Null, Value, ValueRef};
use std::borrow::Cow;
#[cfg(feature = "array")]
use vtab::array::Array;
use Result;
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
@ -14,6 +16,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
@ -61,6 +66,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()),
})
}
}

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

@ -0,0 +1,194 @@
//! 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::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values,
};
use {Connection, 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)
}
lazy_static! {
static ref ARRAY_MODULE: Module<ArrayTab> = eponymous_only_module::<ArrayTab>(1);
}
// 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;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, ArrayTab)> {
let vtab = ArrayTab {
base: ffi::sqlite3_vtab::default(),
};
Ok(("CREATE TABLE x(value,pointer hidden)".to_owned(), 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() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
continue;
}
if let CARRAY_COLUMN_POINTER = constraint.column() {
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: ffi::sqlite3_vtab_cursor::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 {
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[(self.row_id - 1) as usize];
ctx.set_result(&value)
} else {
Ok(())
}
}
}
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
#[cfg(test)]
mod test {
use std::rc::Rc;
use types::Value;
use vtab::array;
use Connection;
#[test]
fn test_array_module() {
let db = Connection::open_in_memory().unwrap();
array::load_module(&db).unwrap();
let v = vec![1i64, 2, 3, 4];
let values = v.into_iter().map(|i| Value::from(i)).collect();
let ptr = Rc::new(values);
{
let mut stmt = db.prepare("SELECT value from rarray(?);").unwrap();
let rows = stmt.query_map(&[&ptr], |row| row.get::<_, i64>(0)).unwrap();
assert_eq!(2, Rc::strong_count(&ptr));
let mut count = 0;
for (i, value) in rows.enumerate() {
assert_eq!(i as i64, value.unwrap() - 1);
count += 1;
}
assert_eq!(4, count);
}
assert_eq!(1, Rc::strong_count(&ptr));
}
}

386
src/vtab/csvtab.rs Normal file
View File

@ -0,0 +1,386 @@
//! CSV Virtual Table.
//!
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C extension.
extern crate csv;
use std::fs::File;
use std::os::raw::c_int;
use std::path::Path;
use std::result;
use std::str;
use ffi;
use types::Null;
use vtab::{
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
Module, VTab, VTabConnection, VTabCursor, Values,
};
use {Connection, Error, Result};
/// Register the "csv" module.
/// ```sql
/// CREATE VIRTUAL TABLE vtab USING csv(
/// filename=FILENAME -- Name of file containing CSV content
/// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);'
/// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no".
/// [, columns=N] -- Assume the CSV file contains N columns.
/// [, delimiter=C] -- CSV delimiter. Default ','.
/// [, quote=C] -- CSV quote. Default '"'. 0 means no quote.
/// );
/// ```
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("csv", &CSV_MODULE, aux)
}
lazy_static! {
static ref CSV_MODULE: Module<CSVTab> = read_only_module::<CSVTab>(1);
}
/// An instance of the CSV virtual table
#[repr(C)]
struct CSVTab {
/// Base class. Must be first
base: ffi::sqlite3_vtab,
/// Name of the CSV file
filename: String,
has_headers: bool,
delimiter: u8,
quote: u8,
/// Offset to start of data
offset_first_row: csv::Position,
}
impl CSVTab {
fn reader(&self) -> result::Result<csv::Reader<File>, csv::Error> {
csv::ReaderBuilder::new()
.has_headers(self.has_headers)
.delimiter(self.delimiter)
.quote(self.quote)
.from_path(&self.filename)
}
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
let arg = try!(str::from_utf8(c_slice)).trim();
let mut split = arg.split('=');
if let Some(key) = split.next() {
if let Some(value) = split.next() {
let param = key.trim();
let value = dequote(value);
return Ok((param, value));
}
}
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
}
fn parse_byte(arg: &str) -> Option<u8> {
if arg.len() == 1 {
arg.bytes().next()
} else {
None
}
}
}
impl VTab for CSVTab {
type Aux = ();
type Cursor = CSVTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
args: &[&[u8]],
) -> Result<(String, CSVTab)> {
if args.len() < 4 {
return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
let mut vtab = CSVTab {
base: ffi::sqlite3_vtab::default(),
filename: "".to_owned(),
has_headers: false,
delimiter: b',',
quote: b'"',
offset_first_row: csv::Position::new(),
};
let mut schema = None;
let mut n_col = None;
let args = &args[3..];
for c_slice in args {
let (param, value) = try!(CSVTab::parameter(c_slice));
match param {
"filename" => {
if !Path::new(value).exists() {
return Err(Error::ModuleError(format!(
"file '{}' does not exist",
value
)));
}
vtab.filename = value.to_owned();
}
"schema" => {
schema = Some(value.to_owned());
}
"columns" => {
if let Ok(n) = value.parse::<u16>() {
if n_col.is_some() {
return Err(Error::ModuleError(
"more than one 'columns' parameter".to_owned(),
));
} else if n == 0 {
return Err(Error::ModuleError(
"must have at least one column".to_owned(),
));
}
n_col = Some(n);
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'columns': {}",
value
)));
}
}
"header" => {
if let Some(b) = parse_boolean(value) {
vtab.has_headers = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'header': {}",
value
)));
}
}
"delimiter" => {
if let Some(b) = CSVTab::parse_byte(value) {
vtab.delimiter = b;
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'delimiter': {}",
value
)));
}
}
"quote" => {
if let Some(b) = CSVTab::parse_byte(value) {
if b == b'0' {
vtab.quote = 0;
} else {
vtab.quote = b;
}
} else {
return Err(Error::ModuleError(format!(
"unrecognized argument to 'quote': {}",
value
)));
}
}
_ => {
return Err(Error::ModuleError(format!(
"unrecognized parameter '{}'",
param
)));
}
}
}
if vtab.filename.is_empty() {
return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
let mut cols: Vec<String> = Vec::new();
if vtab.has_headers || (n_col.is_none() && schema.is_none()) {
let mut reader = try!(vtab.reader());
if vtab.has_headers {
{
let headers = try!(reader.headers());
// headers ignored if cols is not empty
if n_col.is_none() && schema.is_none() {
cols = headers
.into_iter()
.map(|header| escape_double_quote(&header).into_owned())
.collect();
}
}
vtab.offset_first_row = reader.position().clone();
} else {
let mut record = csv::ByteRecord::new();
if try!(reader.read_byte_record(&mut record)) {
for (i, _) in record.iter().enumerate() {
cols.push(format!("c{}", i));
}
}
}
} else if let Some(n_col) = n_col {
for i in 0..n_col {
cols.push(format!("c{}", i));
}
}
if cols.is_empty() && schema.is_none() {
return Err(Error::ModuleError("no column specified".to_owned()));
}
if schema.is_none() {
let mut sql = String::from("CREATE TABLE x(");
for (i, col) in cols.iter().enumerate() {
sql.push('"');
sql.push_str(col);
sql.push_str("\" TEXT");
if i == cols.len() - 1 {
sql.push_str(");");
} else {
sql.push_str(", ");
}
}
schema = Some(sql);
}
Ok((schema.unwrap().to_owned(), vtab))
}
// Only a forward full table scan is supported.
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
info.set_estimated_cost(1_000_000.);
Ok(())
}
fn open(&self) -> Result<CSVTabCursor> {
Ok(CSVTabCursor::new(try!(self.reader())))
}
}
impl CreateVTab for CSVTab {}
/// A cursor for the CSV virtual table
#[repr(C)]
struct CSVTabCursor {
/// Base class. Must be first
base: ffi::sqlite3_vtab_cursor,
/// The CSV reader object
reader: csv::Reader<File>,
/// Current cursor position used as rowid
row_number: usize,
/// Values of the current row
cols: csv::StringRecord,
eof: bool,
}
impl CSVTabCursor {
fn new(reader: csv::Reader<File>) -> CSVTabCursor {
CSVTabCursor {
base: ffi::sqlite3_vtab_cursor::default(),
reader,
row_number: 0,
cols: csv::StringRecord::new(),
eof: false,
}
}
/// Accessor to the associated virtual table.
fn vtab(&self) -> &CSVTab {
unsafe { &*(self.base.pVtab as *const CSVTab) }
}
}
impl VTabCursor for CSVTabCursor {
// Only a full table scan is supported. So `filter` simply rewinds to
// the beginning.
fn filter(&mut self, _idx_num: c_int, _idx_str: Option<&str>, _args: &Values) -> Result<()> {
{
let offset_first_row = self.vtab().offset_first_row.clone();
try!(self.reader.seek(offset_first_row));
}
self.row_number = 0;
self.next()
}
fn next(&mut self) -> Result<()> {
{
self.eof = self.reader.is_done();
if self.eof {
return Ok(());
}
self.eof = !try!(self.reader.read_record(&mut self.cols));
}
self.row_number += 1;
Ok(())
}
fn eof(&self) -> bool {
self.eof
}
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> {
if col < 0 || col as usize >= self.cols.len() {
return Err(Error::ModuleError(format!(
"column index out of bounds: {}",
col
)));
}
if self.cols.is_empty() {
return ctx.set_result(&Null);
}
// TODO Affinity
ctx.set_result(&self.cols[col as usize].to_owned())
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_number as i64)
}
}
impl From<csv::Error> for Error {
fn from(err: csv::Error) -> Error {
use std::error::Error as StdError;
Error::ModuleError(String::from(err.description()))
}
}
#[cfg(test)]
mod test {
use vtab::csvtab;
use {Connection, Result};
#[test]
fn test_csv_module() {
let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.unwrap();
{
let mut s = db.prepare("SELECT rowid, * FROM vtab").unwrap();
{
let headers = s.column_names();
assert_eq!(vec!["rowid", "colA", "colB", "colC"], headers);
}
let ids: Result<Vec<i32>> = s
.query_map(&[], |row| row.get::<_, i32>(0))
.unwrap()
.collect();
let sum = ids.unwrap().iter().fold(0, |acc, &id| acc + id);
assert_eq!(sum, 15);
}
db.execute_batch("DROP TABLE vtab").unwrap();
}
#[test]
fn test_csv_cursor() {
let db = Connection::open_in_memory().unwrap();
csvtab::load_module(&db).unwrap();
db.execute_batch("CREATE VIRTUAL TABLE vtab USING csv(filename='test.csv', header=yes)")
.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();
assert_eq!(row.get::<_, i32>(0), 2);
}
db.execute_batch("DROP TABLE vtab").unwrap();
}
}

991
src/vtab/mod.rs Normal file
View File

@ -0,0 +1,991 @@
//! Create virtual tables.
//!
//! Follow these steps to create your own virtual table:
//! 1. Write implemenation of `VTab` and `VTabCursor` traits.
//! 2. Create an instance of the `Module` structure specialized for `VTab` impl. from step 1.
//! 3. Register your `Module` structure using `Connection.create_module`.
//! 4. Run a `CREATE VIRTUAL TABLE` command that specifies the new module in the `USING` clause.
//!
//! (See [SQLite doc](http://sqlite.org/vtab.html))
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ffi::CString;
use std::marker::PhantomData;
use std::marker::Sync;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::slice;
use context::set_result;
use error::error_from_sqlite_code;
use ffi;
pub use ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
use types::{FromSql, FromSqlError, ToSql, ValueRef};
use {str_to_cstring, Connection, Error, InnerConnection, Result};
// let conn: Connection = ...;
// let mod: Module = ...; // VTab builder
// conn.create_module("module", mod);
//
// conn.execute("CREATE VIRTUAL TABLE foo USING module(...)");
// \-> Module::xcreate
// |-> let vtab: VTab = ...; // on the heap
// \-> conn.declare_vtab("CREATE TABLE foo (...)");
// conn = Connection::open(...);
// \-> Module::xconnect
// |-> let vtab: VTab = ...; // on the heap
// \-> conn.declare_vtab("CREATE TABLE foo (...)");
//
// conn.close();
// \-> vtab.xdisconnect
// conn.execute("DROP TABLE foo");
// \-> vtab.xDestroy
//
// let stmt = conn.prepare("SELECT ... FROM foo WHERE ...");
// \-> vtab.xbestindex
// stmt.query().next();
// \-> vtab.xopen
// |-> let cursor: VTabCursor = ...; // on the heap
// |-> cursor.xfilter or xnext
// |-> cursor.xeof
// \-> if not eof { cursor.column or xrowid } else { cursor.xclose }
//
// db: *mut ffi::sqlite3 => VTabConnection
// module: *const ffi::sqlite3_module => Module
// aux: *mut c_void => Module::Aux
// ffi::sqlite3_vtab => VTab
// ffi::sqlite3_vtab_cursor => VTabCursor
/// Virtual table module
///
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
#[repr(C)]
pub struct Module<T: VTab> {
base: ffi::sqlite3_module,
phantom: PhantomData<T>,
}
unsafe impl<T: VTab> Sync for Module<T> {}
/// Create a read-only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn read_only_module<T: CreateVTab>(version: c_int) -> Module<T> {
// The xConnect and xCreate methods do the same thing, but they must be
// different so that the virtual table is not an eponymous virtual table.
let ffi_module = ffi::sqlite3_module {
iVersion: version,
xCreate: Some(rust_create::<T>),
xConnect: Some(rust_connect::<T>),
xBestIndex: Some(rust_best_index::<T>),
xDisconnect: Some(rust_disconnect::<T>),
xDestroy: Some(rust_destroy::<T>),
xOpen: Some(rust_open::<T>),
xClose: Some(rust_close::<T::Cursor>),
xFilter: Some(rust_filter::<T::Cursor>),
xNext: Some(rust_next::<T::Cursor>),
xEof: Some(rust_eof::<T::Cursor>),
xColumn: Some(rust_column::<T::Cursor>),
xRowid: Some(rust_rowid::<T::Cursor>),
xUpdate: None,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
xSavepoint: None,
xRelease: None,
xRollbackTo: None,
};
Module {
base: ffi_module,
phantom: PhantomData::<T>,
}
}
/// Create an eponymous only virtual table implementation.
///
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn eponymous_only_module<T: VTab>(version: c_int) -> Module<T> {
// A virtual table is eponymous if its xCreate method is the exact same function as the xConnect method
// For eponymous-only virtual tables, the xCreate method is NULL
let ffi_module = ffi::sqlite3_module {
iVersion: version,
xCreate: None,
xConnect: Some(rust_connect::<T>),
xBestIndex: Some(rust_best_index::<T>),
xDisconnect: Some(rust_disconnect::<T>),
xDestroy: None,
xOpen: Some(rust_open::<T>),
xClose: Some(rust_close::<T::Cursor>),
xFilter: Some(rust_filter::<T::Cursor>),
xNext: Some(rust_next::<T::Cursor>),
xEof: Some(rust_eof::<T::Cursor>),
xColumn: Some(rust_column::<T::Cursor>),
xRowid: Some(rust_rowid::<T::Cursor>),
xUpdate: None,
xBegin: None,
xSync: None,
xCommit: None,
xRollback: None,
xFindFunction: None,
xRename: None,
xSavepoint: None,
xRelease: None,
xRollbackTo: None,
};
Module {
base: ffi_module,
phantom: PhantomData::<T>,
}
}
pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection {
// TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
// TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
/// Get access to the underlying SQLite database connection handle.
///
/// # Warning
///
/// You should not need to use this function. If you do need to, please [open an issue
/// on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
/// your use case. This function is unsafe because it gives you raw access to the SQLite
/// connection, and what you do with it could impact the safety of this `Connection`.
pub unsafe fn handle(&mut self) -> *mut ffi::sqlite3 {
self.0
}
}
/// Virtual table instance trait.
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTab {
/// /// Base class. Must be first
/// base: ffi::sqlite3_vtab,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait VTab: Sized {
type Aux;
type Cursor: VTabCursor;
/// Establish a new connection to an existing virtual table.
///
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xconnect_method))
fn connect(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)>;
/// Determine the best way to access the virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xbestindex_method))
fn best_index(&self, info: &mut IndexInfo) -> Result<()>;
/// Create a new cursor used for accessing a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
fn open(&self) -> Result<Self::Cursor>;
}
/// Non-eponymous virtual table instance trait.
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait CreateVTab: VTab {
/// 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.
///
/// Call `connect` by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcreate_method))
fn create(
db: &mut VTabConnection,
aux: Option<&Self::Aux>,
args: &[&[u8]],
) -> Result<(String, Self)> {
Self::connect(db, aux, args)
}
/// Destroy the underlying table implementation. This method undoes the work of `create`.
///
/// Do nothing by default.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method))
fn destroy(&self) -> Result<()> {
Ok(())
}
}
bitflags! {
#[doc = "Index constraint operator."]
#[repr(C)]
pub struct IndexConstraintOp: ::std::os::raw::c_uchar {
const SQLITE_INDEX_CONSTRAINT_EQ = 2;
const SQLITE_INDEX_CONSTRAINT_GT = 4;
const SQLITE_INDEX_CONSTRAINT_LE = 8;
const SQLITE_INDEX_CONSTRAINT_LT = 16;
const SQLITE_INDEX_CONSTRAINT_GE = 32;
const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
}
}
/// Pass information into and receive the reply from the `VTab.best_index` method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo {
/// Record WHERE clause constraints.
pub fn constraints(&self) -> IndexConstraintIter {
let constraints =
unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) };
IndexConstraintIter {
iter: constraints.iter(),
}
}
/// Information about the ORDER BY clause.
pub fn order_bys(&self) -> OrderByIter {
let order_bys =
unsafe { slice::from_raw_parts((*self.0).aOrderBy, (*self.0).nOrderBy as usize) };
OrderByIter {
iter: order_bys.iter(),
}
}
/// Number of terms in the ORDER BY clause
pub fn num_of_order_by(&self) -> usize {
unsafe { (*self.0).nOrderBy as usize }
}
pub fn constraint_usage(&mut self, constraint_idx: usize) -> IndexConstraintUsage {
let constraint_usages = unsafe {
slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize)
};
IndexConstraintUsage(&mut constraint_usages[constraint_idx])
}
/// Number used to identify the index
pub fn set_idx_num(&mut self, idx_num: c_int) {
unsafe {
(*self.0).idxNum = idx_num;
}
}
/// True if output is already ordered
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
unsafe {
(*self.0).orderByConsumed = if order_by_consumed { 1 } else { 0 };
}
}
/// Estimated cost of using this index
pub fn set_estimated_cost(&mut self, estimated_ost: f64) {
unsafe {
(*self.0).estimatedCost = estimated_ost;
}
}
/// Estimated number of rows returned
#[cfg(feature = "bundled")] // SQLite >= 3.8.2
pub fn set_estimated_rows(&mut self, estimated_rows: i64) {
unsafe {
(*self.0).estimatedRows = estimated_rows;
}
}
// TODO idxFlags
// TODO colUsed
// TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
}
pub struct IndexConstraintIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_constraint>,
}
impl<'a> Iterator for IndexConstraintIter<'a> {
type Item = IndexConstraint<'a>;
fn next(&mut self) -> Option<IndexConstraint<'a>> {
self.iter.next().map(|raw| IndexConstraint(raw))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
/// WHERE clause constraint
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
impl<'a> IndexConstraint<'a> {
/// Column constrained. -1 for ROWID
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// Constraint operator
pub fn operator(&self) -> IndexConstraintOp {
IndexConstraintOp::from_bits_truncate(self.0.op)
}
/// True if this constraint is usable
pub fn is_usable(&self) -> bool {
self.0.usable != 0
}
}
/// Information about what parameters to pass to `VTabCursor.filter`.
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
impl<'a> IndexConstraintUsage<'a> {
/// if `argv_index` > 0, constraint is part of argv to `VTabCursor.filter`
pub fn set_argv_index(&mut self, argv_index: c_int) {
self.0.argvIndex = argv_index;
}
/// if `omit`, do not code a test for this constraint
pub fn set_omit(&mut self, omit: bool) {
self.0.omit = if omit { 1 } else { 0 };
}
}
pub struct OrderByIter<'a> {
iter: slice::Iter<'a, ffi::sqlite3_index_info_sqlite3_index_orderby>,
}
impl<'a> Iterator for OrderByIter<'a> {
type Item = OrderBy<'a>;
fn next(&mut self) -> Option<OrderBy<'a>> {
self.iter.next().map(|raw| OrderBy(raw))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
/// A column of the ORDER BY clause.
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
impl<'a> OrderBy<'a> {
/// Column number
pub fn column(&self) -> c_int {
self.0.iColumn
}
/// True for DESC. False for ASC.
pub fn is_order_by_desc(&self) -> bool {
self.0.desc != 0
}
}
/// Virtual table cursor trait.
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTabCursor {
/// /// Base class. Must be first
/// base: ffi::sqlite3_vtab_cursor,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab_cursor.html))
pub trait VTabCursor: Sized {
/// Begin a search of a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xfilter_method))
fn filter(&mut self, idx_num: c_int, idx_str: Option<&str>, args: &Values) -> Result<()>;
/// Advance cursor to the next row of a result set initiated by `filter`.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xnext_method))
fn next(&mut self) -> Result<()>;
/// Must return `false` if the cursor currently points to a valid row of data,
/// or `true` otherwise.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xeof_method))
fn eof(&self) -> bool;
/// Find the value for the `i`-th column of the current row.
/// `i` is zero-based so the first column is numbered 0.
/// May return its result back to SQLite using one of the specified `ctx`.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xcolumn_method))
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()>;
/// Return the rowid of row that the cursor is currently pointing at.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xrowid_method))
fn rowid(&self) -> Result<i64>;
}
/// Context is used by `VTabCursor.column`` to specify the cell value.
pub struct Context(*mut ffi::sqlite3_context);
impl Context {
pub fn set_result<T: ToSql>(&mut self, value: &T) -> Result<()> {
let t = value.to_sql()?;
unsafe { set_result(self.0, &t) };
Ok(())
}
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
}
/// Wrapper to `VTabCursor.filter` arguments, the values requested by `VTab.best_index`.
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
}
impl<'a> Values<'a> {
pub fn len(&self) -> usize {
self.args.len()
}
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
pub fn get<T: FromSql>(&self, idx: usize) -> Result<T> {
let arg = self.args[idx];
let value = unsafe { ValueRef::from_value(arg) };
FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidFilterParameterType(idx, value.data_type()),
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
})
}
// `sqlite3_value_type` returns `SQLITE_NULL` for pointer.
// So it seems not possible to enhance `ValueRef::from_value`.
#[cfg(feature = "array")]
pub(crate) 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 {
let rc = array::Array::from_raw(ptr as *const Vec<Value>);
let array = rc.clone();
array::Array::into_raw(rc); // don't consume it
array
}))
}
}
pub fn iter(&self) -> ValueIter {
ValueIter {
iter: self.args.iter(),
}
}
}
impl<'a> IntoIterator for &'a Values<'a> {
type Item = ValueRef<'a>;
type IntoIter = ValueIter<'a>;
fn into_iter(self) -> ValueIter<'a> {
self.iter()
}
}
pub struct ValueIter<'a> {
iter: slice::Iter<'a, *mut ffi::sqlite3_value>,
}
impl<'a> Iterator for ValueIter<'a> {
type Item = ValueRef<'a>;
fn next(&mut self) -> Option<ValueRef<'a>> {
self.iter
.next()
.map(|&raw| unsafe { ValueRef::from_value(raw) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl Connection {
/// Register a virtual table implementation.
///
/// Step 3 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
pub fn create_module<T: VTab>(
&self,
module_name: &str,
module: &Module<T>,
aux: Option<T::Aux>,
) -> Result<()> {
self.db.borrow_mut().create_module(module_name, module, aux)
}
}
impl InnerConnection {
fn create_module<T: VTab>(
&mut self,
module_name: &str,
module: &Module<T>,
aux: Option<T::Aux>,
) -> Result<()> {
let c_name = try!(str_to_cstring(module_name));
let r = match aux {
Some(aux) => {
let boxed_aux: *mut T::Aux = Box::into_raw(Box::new(aux));
unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
boxed_aux as *mut c_void,
Some(free_boxed_value::<T::Aux>),
)
}
}
None => unsafe {
ffi::sqlite3_create_module_v2(
self.db(),
c_name.as_ptr(),
&module.base,
ptr::null_mut(),
None,
)
},
};
self.decode_result(r)
}
}
/// Escape double-quote (`"`) character occurences by doubling them (`""`).
pub fn escape_double_quote(identifier: &str) -> Cow<str> {
if identifier.contains('"') {
// escape quote by doubling them
Owned(identifier.replace("\"", "\"\""))
} else {
Borrowed(identifier)
}
}
/// Dequote string
pub fn dequote(s: &str) -> &str {
if s.len() < 2 {
return s;
}
match s.bytes().next() {
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
Some(e) if e == b => &s[1..s.len() - 1],
_ => s,
},
_ => s,
}
}
/// The boolean can be one of:
/// ```text
/// 1 yes true on
/// 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")
{
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")
{
Some(false)
} else {
None
}
}
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
let _: Box<T> = Box::from_raw(p as *mut T);
}
unsafe extern "C" fn rust_create<T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: CreateVTab,
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::create(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description());
rc
}
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = mprintf(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_connect<T>(
db: *mut ffi::sqlite3,
aux: *mut c_void,
argc: c_int,
argv: *const *const c_char,
pp_vtab: *mut *mut ffi::sqlite3_vtab,
err_msg: *mut *mut c_char,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
use std::ffi::CStr;
use std::slice;
let mut conn = VTabConnection(db);
let aux = aux as *mut T::Aux;
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
.map(|&cs| CStr::from_ptr(cs).to_bytes()) // FIXME .to_str() -> Result<&str, Utf8Error>
.collect::<Vec<_>>();
match T::connect(&mut conn, aux.as_ref(), &vec[..]) {
Ok((sql, vtab)) => match ::std::ffi::CString::new(sql) {
Ok(c_sql) => {
let rc = ffi::sqlite3_declare_vtab(db, c_sql.as_ptr());
if rc == ffi::SQLITE_OK {
let boxed_vtab: *mut T = Box::into_raw(Box::new(vtab));
*pp_vtab = boxed_vtab as *mut ffi::sqlite3_vtab;
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
*err_msg = mprintf(err.description());
rc
}
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
},
Err(Error::SqliteFailure(err, s)) => {
if let Some(s) = s {
*err_msg = mprintf(&s);
}
err.extended_code
}
Err(err) => {
*err_msg = mprintf(err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_best_index<T>(
vtab: *mut ffi::sqlite3_vtab,
info: *mut ffi::sqlite3_index_info,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
let vt = vtab as *mut T;
let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_disconnect<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: VTab,
{
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vtab = vtab as *mut T;
let _: Box<T> = Box::from_raw(vtab);
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_destroy<T>(vtab: *mut ffi::sqlite3_vtab) -> c_int
where
T: CreateVTab,
{
use std::error::Error as StdError;
if vtab.is_null() {
return ffi::SQLITE_OK;
}
let vt = vtab as *mut T;
match (*vt).destroy() {
Ok(_) => {
let _: Box<T> = Box::from_raw(vt);
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_open<T>(
vtab: *mut ffi::sqlite3_vtab,
pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor,
) -> c_int
where
T: VTab,
{
use std::error::Error as StdError;
let vt = vtab as *mut T;
match (*vt).open() {
Ok(cursor) => {
let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor));
*pp_cursor = boxed_cursor as *mut ffi::sqlite3_vtab_cursor;
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg(vtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg(vtab, err.description());
ffi::SQLITE_ERROR
}
}
}
unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
let _: Box<C> = Box::from_raw(cr);
ffi::SQLITE_OK
}
unsafe extern "C" fn rust_filter<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
idx_num: c_int,
idx_str: *const c_char,
argc: c_int,
argv: *mut *mut ffi::sqlite3_value,
) -> c_int
where
C: VTabCursor,
{
use std::ffi::CStr;
use std::slice;
use std::str;
let idx_name = if idx_str.is_null() {
None
} else {
let c_slice = CStr::from_ptr(idx_str).to_bytes();
Some(str::from_utf8_unchecked(c_slice))
};
let args = slice::from_raw_parts_mut(argv, argc as usize);
let values = Values { args };
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).filter(idx_num, idx_name, &values))
}
unsafe extern "C" fn rust_next<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
cursor_error(cursor, (*cr).next())
}
unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
(*cr).eof() as c_int
}
unsafe extern "C" fn rust_column<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
ctx: *mut ffi::sqlite3_context,
i: c_int,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i))
}
unsafe extern "C" fn rust_rowid<C>(
cursor: *mut ffi::sqlite3_vtab_cursor,
p_rowid: *mut ffi::sqlite3_int64,
) -> c_int
where
C: VTabCursor,
{
let cr = cursor as *mut C;
match (*cr).rowid() {
Ok(rowid) => {
*p_rowid = rowid;
ffi::SQLITE_OK
}
err => cursor_error(cursor, err),
}
}
/// Virtual table cursors can set an error message by assigning a string to `zErrMsg`.
unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
if let Some(err_msg) = s {
set_err_msg((*cursor).pVtab, &err_msg);
}
err.extended_code
}
Err(err) => {
set_err_msg((*cursor).pVtab, err.description());
ffi::SQLITE_ERROR
}
}
}
/// Virtual tables methods can set an error message by assigning a string to `zErrMsg`.
unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
if !(*vtab).zErrMsg.is_null() {
ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
}
(*vtab).zErrMsg = mprintf(err_msg);
}
/// To raise an error, the `column` method should use this method to set the error message
/// and return the error code.
unsafe fn result_error<T>(ctx: *mut ffi::sqlite3_context, result: Result<T>) -> c_int {
use std::error::Error as StdError;
match result {
Ok(_) => ffi::SQLITE_OK,
Err(Error::SqliteFailure(err, s)) => {
match err.extended_code {
ffi::SQLITE_TOOBIG => {
ffi::sqlite3_result_error_toobig(ctx);
}
ffi::SQLITE_NOMEM => {
ffi::sqlite3_result_error_nomem(ctx);
}
code => {
ffi::sqlite3_result_error_code(ctx, code);
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
};
err.extended_code
}
Err(err) => {
ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_ERROR);
if let Ok(cstr) = str_to_cstring(err.description()) {
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
ffi::SQLITE_ERROR
}
}
}
// Space to hold this error message string must be obtained
// from an SQLite memory allocation function.
fn mprintf(err_msg: &str) -> *mut c_char {
let c_format = CString::new("%s").unwrap();
let c_err = CString::new(err_msg).unwrap();
unsafe { ffi::sqlite3_mprintf(c_format.as_ptr(), c_err.as_ptr()) }
}
#[cfg(feature = "array")]
pub mod array;
#[cfg(feature = "csvtab")]
pub mod csvtab;
#[cfg(feature = "bundled")]
pub mod series; // SQLite >= 3.9.0
#[cfg(test)]
mod test {
#[test]
fn test_dequote() {
assert_eq!("", super::dequote(""));
assert_eq!("'", super::dequote("'"));
assert_eq!("\"", super::dequote("\""));
assert_eq!("'\"", super::dequote("'\""));
assert_eq!("", super::dequote("''"));
assert_eq!("", super::dequote("\"\""));
assert_eq!("x", super::dequote("'x'"));
assert_eq!("x", super::dequote("\"x\""));
assert_eq!("x", super::dequote("x"));
}
#[test]
fn test_parse_boolean() {
assert_eq!(None, super::parse_boolean(""));
assert_eq!(Some(true), super::parse_boolean("1"));
assert_eq!(Some(true), super::parse_boolean("yes"));
assert_eq!(Some(true), super::parse_boolean("on"));
assert_eq!(Some(true), super::parse_boolean("true"));
assert_eq!(Some(false), super::parse_boolean("0"));
assert_eq!(Some(false), super::parse_boolean("no"));
assert_eq!(Some(false), super::parse_boolean("off"));
assert_eq!(Some(false), super::parse_boolean("false"));
}
}

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

@ -0,0 +1,286 @@
//! 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 std::os::raw::c_int;
use ffi;
use types::Type;
use vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, Module, VTab, VTabConnection,
VTabCursor, Values,
};
use {Connection, Result};
/// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("generate_series", &SERIES_MODULE, aux)
}
lazy_static! {
static ref SERIES_MODULE: Module<SeriesTab> = eponymous_only_module::<SeriesTab>(1);
}
// Column numbers
// const SERIES_COLUMN_VALUE : c_int = 0;
const SERIES_COLUMN_START: c_int = 1;
const SERIES_COLUMN_STOP: c_int = 2;
const SERIES_COLUMN_STEP: c_int = 3;
bitflags! {
#[repr(C)]
struct QueryPlanFlags: ::std::os::raw::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 = QueryPlanFlags::START.bits | QueryPlanFlags::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 {
type Aux = ();
type Cursor = SeriesTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, SeriesTab)> {
let vtab = SeriesTab {
base: ffi::sqlite3_vtab::default(),
};
Ok((
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
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, constraint) in info.constraints().enumerate() {
if !constraint.is_usable() {
continue;
}
if constraint.operator() != IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ {
continue;
}
match constraint.column() {
SERIES_COLUMN_START => {
start_idx = Some(i);
idx_num |= QueryPlanFlags::START;
}
SERIES_COLUMN_STOP => {
stop_idx = Some(i);
idx_num |= QueryPlanFlags::STOP;
}
SERIES_COLUMN_STEP => {
step_idx = Some(i);
idx_num |= QueryPlanFlags::STEP;
}
_ => {}
};
}
let mut num_of_arg = 0;
if let Some(start_idx) = start_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(start_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if let Some(stop_idx) = stop_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(stop_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if let Some(step_idx) = step_idx {
num_of_arg += 1;
let mut constraint_usage = info.constraint_usage(step_idx);
constraint_usage.set_argv_index(num_of_arg);
constraint_usage.set_omit(true);
}
if idx_num.contains(QueryPlanFlags::BOTH) {
// Both start= and stop= boundaries are available.
info.set_estimated_cost(f64::from(
2 - if idx_num.contains(QueryPlanFlags::STEP) {
1
} else {
0
},
));
info.set_estimated_rows(1000);
let order_by_consumed = {
let mut order_bys = info.order_bys();
if let Some(order_by) = order_bys.next() {
if order_by.is_order_by_desc() {
idx_num |= QueryPlanFlags::DESC;
}
true
} else {
false
}
};
if order_by_consumed {
info.set_order_by_consumed(true);
}
} else {
info.set_estimated_cost(2_147_483_647f64);
info.set_estimated_rows(2_147_483_647);
}
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 {
SeriesTabCursor::default()
}
}
impl VTabCursor for SeriesTabCursor {
fn filter(&mut self, idx_num: 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(QueryPlanFlags::START) {
self.min_value = try!(args.get(i));
i += 1;
} else {
self.min_value = 0;
}
if idx_num.contains(QueryPlanFlags::STOP) {
self.max_value = try!(args.get(i));
i += 1;
} else {
self.max_value = 0xffff_ffff;
}
if idx_num.contains(QueryPlanFlags::STEP) {
self.step = try!(args.get(i));
if self.step < 1 {
self.step = 1;
}
} else {
self.step = 1;
};
for arg in args.iter() {
if arg.data_type() == Type::Null {
// If any of the constraints have a NULL value, then return no rows.
self.min_value = 1;
self.max_value = 0;
break;
}
}
self.is_desc = idx_num.contains(QueryPlanFlags::DESC);
if self.is_desc {
self.value = self.max_value;
if self.step > 0 {
self.value -= (self.max_value - self.min_value) % self.step;
}
} else {
self.value = self.min_value;
}
self.row_id = 1;
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: 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)
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
#[cfg(test)]
mod test {
use ffi;
use vtab::series;
use Connection;
#[test]
fn test_series_module() {
let version = unsafe { ffi::sqlite3_libversion_number() };
if version < 3008012 {
return;
}
let db = Connection::open_in_memory().unwrap();
series::load_module(&db).unwrap();
let mut s = db.prepare("SELECT * FROM generate_series(0,20,5)").unwrap();
let series = s.query_map(&[], |row| row.get::<_, i32>(0)).unwrap();
let mut expected = 0;
for value in series {
assert_eq!(expected, value.unwrap());
expected += 5;
}
}
}

6
test.csv Normal file
View File

@ -0,0 +1,6 @@
"colA","colB","colC"
1,2,3
a,b,c
a,"b",c
"a","b","c .. z"
"a","b","c,d"
1 colA colB colC
2 1 2 3
3 a b c
4 a b c
5 a b c .. z
6 a b c,d

97
tests/vtab.rs Normal file
View File

@ -0,0 +1,97 @@
//! Ensure Virtual tables can be declared outside `rusqlite` crate.
#[cfg(feature = "vtab")]
extern crate rusqlite;
#[cfg(feature = "vtab")]
#[test]
fn test_dummy_module() {
use rusqlite::vtab::{
eponymous_only_module, sqlite3_vtab, sqlite3_vtab_cursor, Context, IndexInfo, VTab,
VTabConnection, VTabCursor, Values,
};
use rusqlite::{version_number, Connection, Result};
use std::os::raw::c_int;
let module = eponymous_only_module::<DummyTab>(1);
#[repr(C)]
struct DummyTab {
/// Base class. Must be first
base: sqlite3_vtab,
}
impl VTab for DummyTab {
type Aux = ();
type Cursor = DummyTabCursor;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, DummyTab)> {
let vtab = DummyTab {
base: sqlite3_vtab::default(),
};
Ok(("CREATE TABLE x(value)".to_owned(), vtab))
}
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
info.set_estimated_cost(1.);
Ok(())
}
fn open(&self) -> Result<DummyTabCursor> {
Ok(DummyTabCursor::default())
}
}
#[derive(Default)]
#[repr(C)]
struct DummyTabCursor {
/// Base class. Must be first
base: sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
}
impl VTabCursor for DummyTabCursor {
fn filter(
&mut self,
_idx_num: c_int,
_idx_str: Option<&str>,
_args: &Values,
) -> Result<()> {
self.row_id = 1;
Ok(())
}
fn next(&mut self) -> Result<()> {
self.row_id += 1;
Ok(())
}
fn eof(&self) -> bool {
self.row_id > 1
}
fn column(&self, ctx: &mut Context, _: c_int) -> Result<()> {
ctx.set_result(&self.row_id)
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_id)
}
}
let db = Connection::open_in_memory().unwrap();
db.create_module::<DummyTab>("dummy", &module, None)
.unwrap();
let version = version_number();
if version < 3008012 {
return;
}
let mut s = db.prepare("SELECT * FROM dummy()").unwrap();
let dummy = s.query_row(&[], |row| row.get::<_, i32>(0)).unwrap();
assert_eq!(1, dummy);
}