mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-30 06:01:36 +08:00
1067 lines
33 KiB
Rust
1067 lines
33 KiB
Rust
//! `feature = "vtab"` 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::marker::PhantomData;
|
|
use std::marker::Sync;
|
|
use std::os::raw::{c_char, c_int, c_void};
|
|
use std::ptr;
|
|
use std::slice;
|
|
|
|
use crate::context::set_result;
|
|
use crate::error::error_from_sqlite_code;
|
|
use crate::ffi;
|
|
pub use crate::ffi::{sqlite3_vtab, sqlite3_vtab_cursor};
|
|
use crate::types::{FromSql, FromSqlError, ToSql, ValueRef};
|
|
use crate::{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
|
|
|
|
/// `feature = "vtab"` Virtual table module
|
|
///
|
|
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
|
|
#[repr(transparent)]
|
|
pub struct Module<T: VTab> {
|
|
base: ffi::sqlite3_module,
|
|
phantom: PhantomData<T>,
|
|
}
|
|
|
|
unsafe impl<T: VTab> Send for Module<T> {}
|
|
unsafe impl<T: VTab> Sync for Module<T> {}
|
|
|
|
union ModuleZeroHack {
|
|
bytes: [u8; std::mem::size_of::<ffi::sqlite3_module>()],
|
|
module: ffi::sqlite3_module,
|
|
}
|
|
|
|
// Used as a trailing initializer for sqlite3_module -- this way we avoid having
|
|
// the build fail if buildtime_bindgen is on. This is safe, as bindgen-generated
|
|
// structs are allowed to be zeroed.
|
|
const ZERO_MODULE: ffi::sqlite3_module = unsafe {
|
|
ModuleZeroHack {
|
|
bytes: [0u8; std::mem::size_of::<ffi::sqlite3_module>()],
|
|
}
|
|
.module
|
|
};
|
|
|
|
/// `feature = "vtab"` 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>() -> &'static 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.
|
|
&Module {
|
|
base: ffi::sqlite3_module {
|
|
// We don't use V3
|
|
iVersion: 2, // We don't use V2 or V3 features in read_only_module types
|
|
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,
|
|
..ZERO_MODULE
|
|
},
|
|
phantom: PhantomData::<T>,
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` 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>() -> &'static 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
|
|
&Module {
|
|
base: ffi::sqlite3_module {
|
|
// We don't use V3
|
|
iVersion: 2,
|
|
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,
|
|
..ZERO_MODULE
|
|
},
|
|
phantom: PhantomData::<T>,
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"`
|
|
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/rusqlite/rusqlite/issues) and describe
|
|
/// your use case.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// 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
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` Virtual table instance trait.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The first item in a struct implementing VTab must be
|
|
/// `rusqlite::sqlite3_vtab`, and the struct must be `#[repr(C)]`.
|
|
///
|
|
/// ```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 unsafe 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>;
|
|
}
|
|
|
|
/// `feature = "vtab"` 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(())
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` Index constraint operator.
|
|
#[derive(Debug, PartialEq)]
|
|
#[allow(non_snake_case, non_camel_case_types)]
|
|
#[non_exhaustive]
|
|
pub enum IndexConstraintOp {
|
|
SQLITE_INDEX_CONSTRAINT_EQ,
|
|
SQLITE_INDEX_CONSTRAINT_GT,
|
|
SQLITE_INDEX_CONSTRAINT_LE,
|
|
SQLITE_INDEX_CONSTRAINT_LT,
|
|
SQLITE_INDEX_CONSTRAINT_GE,
|
|
SQLITE_INDEX_CONSTRAINT_MATCH,
|
|
SQLITE_INDEX_CONSTRAINT_LIKE, // 3.10.0
|
|
SQLITE_INDEX_CONSTRAINT_GLOB, // 3.10.0
|
|
SQLITE_INDEX_CONSTRAINT_REGEXP, // 3.10.0
|
|
SQLITE_INDEX_CONSTRAINT_NE, // 3.21.0
|
|
SQLITE_INDEX_CONSTRAINT_ISNOT, // 3.21.0
|
|
SQLITE_INDEX_CONSTRAINT_ISNOTNULL, // 3.21.0
|
|
SQLITE_INDEX_CONSTRAINT_ISNULL, // 3.21.0
|
|
SQLITE_INDEX_CONSTRAINT_IS, // 3.21.0
|
|
SQLITE_INDEX_CONSTRAINT_FUNCTION(u8), // 3.25.0
|
|
}
|
|
|
|
impl From<u8> for IndexConstraintOp {
|
|
fn from(code: u8) -> IndexConstraintOp {
|
|
match code {
|
|
2 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_EQ,
|
|
4 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GT,
|
|
8 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LE,
|
|
16 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LT,
|
|
32 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GE,
|
|
64 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_MATCH,
|
|
65 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIKE,
|
|
66 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_GLOB,
|
|
67 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_REGEXP,
|
|
68 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_NE,
|
|
69 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOT,
|
|
70 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNOTNULL,
|
|
71 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_ISNULL,
|
|
72 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_IS,
|
|
v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` 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 = "modern_sqlite")] // 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)
|
|
}
|
|
|
|
/// `feature = "vtab"`
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` WHERE clause constraint.
|
|
pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint);
|
|
|
|
impl IndexConstraint<'_> {
|
|
/// Column constrained. -1 for ROWID
|
|
pub fn column(&self) -> c_int {
|
|
self.0.iColumn
|
|
}
|
|
|
|
/// Constraint operator
|
|
pub fn operator(&self) -> IndexConstraintOp {
|
|
IndexConstraintOp::from(self.0.op)
|
|
}
|
|
|
|
/// True if this constraint is usable
|
|
pub fn is_usable(&self) -> bool {
|
|
self.0.usable != 0
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` Information about what parameters to pass to
|
|
/// `VTabCursor.filter`.
|
|
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
|
|
|
|
impl IndexConstraintUsage<'_> {
|
|
/// 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 };
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"`
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` A column of the ORDER BY clause.
|
|
pub struct OrderBy<'a>(&'a ffi::sqlite3_index_info_sqlite3_index_orderby);
|
|
|
|
impl OrderBy<'_> {
|
|
/// 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
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` 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 unsafe 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>;
|
|
}
|
|
|
|
/// `feature = "vtab"` 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)
|
|
}
|
|
|
|
/// `feature = "vtab"` Wrapper to `VTabCursor.filter` arguments, the values
|
|
/// requested by `VTab.best_index`.
|
|
pub struct Values<'a> {
|
|
args: &'a [*mut ffi::sqlite3_value],
|
|
}
|
|
|
|
impl Values<'_> {
|
|
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),
|
|
#[cfg(feature = "i128_blob")]
|
|
FromSqlError::InvalidI128Size(_) => {
|
|
Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
|
|
}
|
|
#[cfg(feature = "uuid")]
|
|
FromSqlError::InvalidUuidSize(_) => {
|
|
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
|
|
}
|
|
})
|
|
}
|
|
|
|
// `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 crate::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 IntoIter = ValueIter<'a>;
|
|
type Item = ValueRef<'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 {
|
|
/// `feature = "vtab"` 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: &'static 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: &'static Module<T>,
|
|
aux: Option<T::Aux>,
|
|
) -> Result<()> {
|
|
let c_name = 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)
|
|
}
|
|
}
|
|
|
|
/// `feature = "vtab"` 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)
|
|
}
|
|
}
|
|
/// `feature = "vtab"` 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,
|
|
}
|
|
}
|
|
/// `feature = "vtab"` 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::ffi::CStr;
|
|
|
|
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 = alloc(&err.to_string());
|
|
rc
|
|
}
|
|
}
|
|
Err(err) => {
|
|
*err_msg = alloc(&err.to_string());
|
|
ffi::SQLITE_ERROR
|
|
}
|
|
},
|
|
Err(Error::SqliteFailure(err, s)) => {
|
|
if let Some(s) = s {
|
|
*err_msg = alloc(&s);
|
|
}
|
|
err.extended_code
|
|
}
|
|
Err(err) => {
|
|
*err_msg = alloc(&err.to_string());
|
|
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::ffi::CStr;
|
|
|
|
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 = alloc(&err.to_string());
|
|
rc
|
|
}
|
|
}
|
|
Err(err) => {
|
|
*err_msg = alloc(&err.to_string());
|
|
ffi::SQLITE_ERROR
|
|
}
|
|
},
|
|
Err(Error::SqliteFailure(err, s)) => {
|
|
if let Some(s) = s {
|
|
*err_msg = alloc(&s);
|
|
}
|
|
err.extended_code
|
|
}
|
|
Err(err) => {
|
|
*err_msg = alloc(&err.to_string());
|
|
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,
|
|
{
|
|
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.to_string());
|
|
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,
|
|
{
|
|
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.to_string());
|
|
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,
|
|
{
|
|
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.to_string());
|
|
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::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 {
|
|
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.to_string());
|
|
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 = alloc(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 {
|
|
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.to_string()) {
|
|
ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
|
|
}
|
|
ffi::SQLITE_ERROR
|
|
}
|
|
}
|
|
}
|
|
|
|
// Space to hold this string must be obtained
|
|
// from an SQLite memory allocation function
|
|
fn alloc(s: &str) -> *mut c_char {
|
|
crate::util::SqliteMallocString::from_str(s).into_raw()
|
|
}
|
|
|
|
#[cfg(feature = "array")]
|
|
pub mod array;
|
|
#[cfg(feature = "csvtab")]
|
|
pub mod csvtab;
|
|
#[cfg(feature = "series")]
|
|
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"));
|
|
}
|
|
}
|