mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +08:00
commit
8c0183482f
10
.travis.yml
10
.travis.yml
@ -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"
|
||||
|
11
Cargo.toml
11
Cargo.toml
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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 = []
|
||||
|
2145
libsqlite3-sys/bindgen-bindings/bindgen_3.7.7.rs
Normal file
2145
libsqlite3-sys/bindgen-bindings/bindgen_3.7.7.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
];
|
||||
|
@ -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
124
src/context.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/error.rs
25
src/error.rs
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
108
src/functions.rs
108
src/functions.rs
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) },
|
||||
|
@ -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
194
src/vtab/array.rs
Normal 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
386
src/vtab/csvtab.rs
Normal 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
991
src/vtab/mod.rs
Normal 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
286
src/vtab/series.rs
Normal 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
6
test.csv
Normal 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"
|
|
97
tests/vtab.rs
Normal file
97
tests/vtab.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user