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
|
sudo: false
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
language: rust
|
language: rust
|
||||||
|
|
||||||
@ -39,7 +38,8 @@ script:
|
|||||||
- cargo test --features bundled
|
- cargo test --features bundled
|
||||||
- cargo test --features sqlcipher
|
- cargo test --features sqlcipher
|
||||||
- cargo test --features "unlock_notify bundled"
|
- cargo test --features "unlock_notify bundled"
|
||||||
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace"
|
- cargo test --features "array csvtab vtab"
|
||||||
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
|
||||||
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled"
|
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
|
||||||
- cargo test --features "backup blob chrono functions hooks limits load_extension serde_json trace bundled 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 = []
|
hooks = []
|
||||||
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
||||||
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
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]
|
[dependencies]
|
||||||
time = "0.1.0"
|
time = "0.1.0"
|
||||||
@ -36,6 +40,8 @@ bitflags = "1.0"
|
|||||||
lru-cache = "0.1"
|
lru-cache = "0.1"
|
||||||
chrono = { version = "0.4", optional = true }
|
chrono = { version = "0.4", optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
csv = { version = "1.0", optional = true }
|
||||||
|
lazy_static = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
@ -53,8 +59,11 @@ harness = false
|
|||||||
[[test]]
|
[[test]]
|
||||||
name = "deny_single_threaded_sqlite_config"
|
name = "deny_single_threaded_sqlite_config"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "vtab"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[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
|
all-features = false
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
@ -34,10 +34,10 @@ build: false
|
|||||||
test_script:
|
test_script:
|
||||||
- cargo test --lib --verbose
|
- cargo test --lib --verbose
|
||||||
- cargo test --lib --verbose --features bundled
|
- 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 csvtab functions hooks limits load_extension serde_json trace vtab"
|
||||||
- cargo test --lib --features "backup blob chrono functions hooks limits load_extension serde_json trace buildtime_bindgen"
|
- 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 functions hooks limits load_extension serde_json trace bundled"
|
- 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 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 bundled buildtime_bindgen"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- C:\Users\appveyor\.cargo
|
- 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_6_23 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_3 = ["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_4 = ["pkg-config", "vcpkg"]
|
||||||
|
min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
|
||||||
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
|
||||||
# sqlite3_unlock_notify >= 3.6.12
|
# sqlite3_unlock_notify >= 3.6.12
|
||||||
unlock_notify = []
|
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] = &[
|
static PREBUILT_BINDGEN_PATHS: &'static [&'static str] = &[
|
||||||
"bindgen-bindings/bindgen_3.6.8.rs",
|
"bindgen-bindings/bindgen_3.6.8.rs",
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_6_11")]
|
#[cfg(feature = "min_sqlite_version_3_6_11")]
|
||||||
"bindgen-bindings/bindgen_3.6.11.rs",
|
"bindgen-bindings/bindgen_3.6.11.rs",
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_6_23")]
|
#[cfg(feature = "min_sqlite_version_3_6_23")]
|
||||||
"bindgen-bindings/bindgen_3.6.23.rs",
|
"bindgen-bindings/bindgen_3.6.23.rs",
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_3")]
|
#[cfg(feature = "min_sqlite_version_3_7_3")]
|
||||||
"bindgen-bindings/bindgen_3.7.3.rs",
|
"bindgen-bindings/bindgen_3.7.3.rs",
|
||||||
|
|
||||||
#[cfg(feature = "min_sqlite_version_3_7_4")]
|
#[cfg(feature = "min_sqlite_version_3_7_4")]
|
||||||
"bindgen-bindings/bindgen_3.7.4.rs",
|
"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")]
|
#[cfg(feature = "min_sqlite_version_3_7_16")]
|
||||||
"bindgen-bindings/bindgen_3.7.16.rs",
|
"bindgen-bindings/bindgen_3.7.16.rs",
|
||||||
];
|
];
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub use self::error::*;
|
pub use self::error::*;
|
||||||
|
|
||||||
|
use std::default::Default;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
@ -45,3 +46,18 @@ pub enum Limit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));
|
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.
|
/// to the requested type.
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
InvalidFunctionParameterType(usize, Type),
|
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.,
|
/// An error case available for implementors of custom user functions (e.g.,
|
||||||
/// `create_scalar_function`).
|
/// `create_scalar_function`).
|
||||||
@ -80,6 +84,12 @@ pub enum Error {
|
|||||||
|
|
||||||
/// Error when the SQL is not a `SELECT`, is not read-only.
|
/// Error when the SQL is not a `SELECT`, is not read-only.
|
||||||
InvalidQuery,
|
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 {
|
impl From<str::Utf8Error> for Error {
|
||||||
@ -130,10 +140,16 @@ impl fmt::Display for Error {
|
|||||||
Error::InvalidFunctionParameterType(i, ref t) => {
|
Error::InvalidFunctionParameterType(i, ref t) => {
|
||||||
write!(f, "Invalid function parameter type {} at index {}", t, i)
|
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")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => err.fmt(f),
|
Error::UserFunctionError(ref err) => err.fmt(f),
|
||||||
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
|
Error::ToSqlConversionFailure(ref err) => err.fmt(f),
|
||||||
Error::InvalidQuery => write!(f, "Query is not read-only"),
|
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")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
Error::InvalidFunctionParameterType(_, _) => "invalid function parameter type",
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::InvalidFilterParameterType(_, _) => "invalid filter parameter type",
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => err.description(),
|
Error::UserFunctionError(ref err) => err.description(),
|
||||||
Error::ToSqlConversionFailure(ref err) => err.description(),
|
Error::ToSqlConversionFailure(ref err) => err.description(),
|
||||||
Error::InvalidQuery => "query is not read-only",
|
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")]
|
#[cfg(feature = "functions")]
|
||||||
Error::InvalidFunctionParameterType(_, _) => None,
|
Error::InvalidFunctionParameterType(_, _) => None,
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
Error::InvalidFilterParameterType(_, _) => None,
|
||||||
|
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
Error::UserFunctionError(ref err) => Some(&**err),
|
Error::UserFunctionError(ref err) => Some(&**err),
|
||||||
|
|
||||||
Error::FromSqlConversionFailure(_, _, ref err)
|
Error::FromSqlConversionFailure(_, _, ref err)
|
||||||
| Error::ToSqlConversionFailure(ref err) => Some(&**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::error::Error as StdError;
|
||||||
use std::ffi::CStr;
|
use std::os::raw::{c_int, c_void};
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
@ -59,61 +58,11 @@ use ffi;
|
|||||||
use ffi::sqlite3_context;
|
use ffi::sqlite3_context;
|
||||||
use ffi::sqlite3_value;
|
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};
|
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) {
|
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
|
// 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
|
// 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) {
|
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||||
drop(Box::from_raw(p as *mut T));
|
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
|
/// 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:
|
/// 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;
|
fn init(&self) -> A;
|
||||||
|
|
||||||
/// "step" function called once for each row in an aggregate group. May be called
|
/// "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;
|
extern crate lru_cache;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[cfg(test)]
|
#[cfg(any(test, feature = "vtab"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
@ -108,6 +108,8 @@ pub mod backup;
|
|||||||
pub mod blob;
|
pub mod blob;
|
||||||
mod busy;
|
mod busy;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
#[cfg(any(feature = "functions", feature = "vtab"))]
|
||||||
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
#[cfg(feature = "functions")]
|
#[cfg(feature = "functions")]
|
||||||
pub mod functions;
|
pub mod functions;
|
||||||
@ -126,6 +128,8 @@ mod transaction;
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
mod unlock_notify;
|
mod unlock_notify;
|
||||||
mod version;
|
mod version;
|
||||||
|
#[cfg(feature = "vtab")]
|
||||||
|
pub mod vtab;
|
||||||
|
|
||||||
// Number of cached prepared statements we'll hold on to.
|
// Number of cached prepared statements we'll hold on to.
|
||||||
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
|
const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::os::raw::{c_char, c_int, c_void};
|
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::slice::from_raw_parts;
|
||||||
use std::{convert, fmt, mem, ptr, result, str};
|
use std::{convert, fmt, mem, ptr, result, str};
|
||||||
|
|
||||||
@ -9,6 +11,8 @@ use super::{
|
|||||||
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
|
||||||
};
|
};
|
||||||
use types::{ToSql, ToSqlOutput};
|
use types::{ToSql, ToSqlOutput};
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
use vtab::array::{free_array, ARRAY_TYPE};
|
||||||
|
|
||||||
/// A prepared statement.
|
/// A prepared statement.
|
||||||
pub struct Statement<'conn> {
|
pub struct Statement<'conn> {
|
||||||
@ -419,6 +423,18 @@ impl<'conn> Statement<'conn> {
|
|||||||
.conn
|
.conn
|
||||||
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
|
.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 {
|
self.conn.decode_result(match value {
|
||||||
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },
|
ValueRef::Null => unsafe { ffi::sqlite3_bind_null(ptr, col as c_int) },
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use super::{Null, Value, ValueRef};
|
use super::{Null, Value, ValueRef};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
use vtab::array::Array;
|
||||||
use Result;
|
use Result;
|
||||||
|
|
||||||
/// `ToSqlOutput` represents the possible output types for implementors of the `ToSql` trait.
|
/// `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.
|
/// A BLOB of the given length that is filled with zeroes.
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ZeroBlob(i32),
|
ZeroBlob(i32),
|
||||||
|
|
||||||
|
#[cfg(feature = "array")]
|
||||||
|
Array(Array),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generically allow any type that can be converted into a ValueRef
|
// Generically allow any type that can be converted into a ValueRef
|
||||||
@ -61,6 +66,8 @@ impl<'a> ToSql for ToSqlOutput<'a> {
|
|||||||
|
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i),
|
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