diff options
Diffstat (limited to 'third_party/rust/rusqlite/src/vtab/mod.rs')
-rw-r--r-- | third_party/rust/rusqlite/src/vtab/mod.rs | 1366 |
1 files changed, 1366 insertions, 0 deletions
diff --git a/third_party/rust/rusqlite/src/vtab/mod.rs b/third_party/rust/rusqlite/src/vtab/mod.rs new file mode 100644 index 0000000000..07008f3637 --- /dev/null +++ b/third_party/rust/rusqlite/src/vtab/mod.rs @@ -0,0 +1,1366 @@ +//! Create virtual tables. +//! +//! Follow these steps to create your own virtual table: +//! 1. Write implementation 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 + +/// Virtual table kind +pub enum VTabKind { + /// Non-eponymous + Default, + /// [`create`](CreateVTab::create) == [`connect`](VTab::connect) + /// + /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_virtual_tables) + Eponymous, + /// No [`create`](CreateVTab::create) / [`destroy`](CreateVTab::destroy) or + /// not used + /// + /// SQLite >= 3.9.0 + /// + /// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_only_virtual_tables) + EponymousOnly, +} + +/// Virtual table module +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/module.html)) +#[repr(transparent)] +pub struct Module<'vtab, T: VTab<'vtab>> { + base: ffi::sqlite3_module, + phantom: PhantomData<&'vtab T>, +} + +unsafe impl<'vtab, T: VTab<'vtab>> Send for Module<'vtab, T> {} +unsafe impl<'vtab, T: VTab<'vtab>> Sync for Module<'vtab, 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: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()], + } + .module +}; + +macro_rules! module { + ($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => { + #[allow(clippy::needless_update)] + &Module { + base: ffi::sqlite3_module { + // We don't use V3 + iVersion: 2, + xCreate: $xc, + xConnect: Some(rust_connect::<$vt>), + xBestIndex: Some(rust_best_index::<$vt>), + xDisconnect: Some(rust_disconnect::<$vt>), + xDestroy: $xd, + xOpen: Some(rust_open::<$vt>), + xClose: Some(rust_close::<$ct>), + xFilter: Some(rust_filter::<$ct>), + xNext: Some(rust_next::<$ct>), + xEof: Some(rust_eof::<$ct>), + xColumn: Some(rust_column::<$ct>), + xRowid: Some(rust_rowid::<$ct>), // FIXME optional + xUpdate: $xu, + xBegin: None, + xSync: None, + xCommit: None, + xRollback: None, + xFindFunction: None, + xRename: None, + xSavepoint: None, + xRelease: None, + xRollbackTo: None, + ..ZERO_MODULE + }, + phantom: PhantomData::<&$lt $vt>, + } + }; +} + +/// Create an modifiable virtual table implementation. +/// +/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations). +#[must_use] +pub fn update_module<'vtab, T: UpdateVTab<'vtab>>() -> &'static Module<'vtab, T> { + match T::KIND { + VTabKind::EponymousOnly => { + module!('vtab, T, T::Cursor, None, None, Some(rust_update::<T>)) + } + VTabKind::Eponymous => { + module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), Some(rust_update::<T>)) + } + _ => { + module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), Some(rust_update::<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). +#[must_use] +pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> { + match T::KIND { + VTabKind::EponymousOnly => eponymous_only_module(), + VTabKind::Eponymous => { + // A virtual table is eponymous if its xCreate method is the exact same function + // as the xConnect method + module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), None) + } + _ => { + // The xConnect and xCreate methods may do the same thing, but they must be + // different so that the virtual table is not an eponymous virtual table. + module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), None) + } + } +} + +/// 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). +#[must_use] +pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> { + // For eponymous-only virtual tables, the xCreate method is NULL + module!('vtab, T, T::Cursor, None, None, None) +} + +/// Virtual table configuration options +#[repr(i32)] +#[non_exhaustive] +#[cfg(feature = "modern_sqlite")] // 3.7.7 +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum VTabConfig { + /// Equivalent to SQLITE_VTAB_CONSTRAINT_SUPPORT + ConstraintSupport = 1, + /// Equivalent to SQLITE_VTAB_INNOCUOUS + Innocuous = 2, + /// Equivalent to SQLITE_VTAB_DIRECTONLY + DirectOnly = 3, +} + +/// `feature = "vtab"` +pub struct VTabConnection(*mut ffi::sqlite3); + +impl VTabConnection { + /// Configure various facets of the virtual table interface + #[cfg(feature = "modern_sqlite")] // 3.7.7 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn config(&mut self, config: VTabConfig) -> Result<()> { + crate::error::check(unsafe { ffi::sqlite3_vtab_config(self.0, config as c_int) }) + } + + // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html) & xUpdate + + /// 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 + } +} + +/// Eponymous-only 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: rusqlite::vtab::sqlite3_vtab, +/// /* Virtual table implementations will typically add additional fields */ +/// } +/// ``` +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) +pub unsafe trait VTab<'vtab>: Sized { + /// Client data passed to [`Connection::create_module`]. + type Aux; + /// Specific cursor implementation + 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(&'vtab mut self) -> Result<Self::Cursor>; +} + +/// Read-only virtual table instance trait. +/// +/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html)) +pub trait CreateVTab<'vtab>: VTab<'vtab> { + /// For [`EponymousOnly`](VTabKind::EponymousOnly), + /// [`create`](CreateVTab::create) and [`destroy`](CreateVTab::destroy) are + /// not called + const KIND: VTabKind; + /// 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`](VTab::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`](CreateVTab::create). + /// + /// Do nothing by default. + /// (See [SQLite doc](https://sqlite.org/vtab.html#the_xdestroy_method)) + fn destroy(&self) -> Result<()> { + Ok(()) + } +} + +/// Writable virtual table instance trait. +/// +/// (See [SQLite doc](https://sqlite.org/vtab.html#xupdate)) +pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> { + /// Delete rowid or PK + fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>; + /// Insert: `args[0] == NULL: old rowid or PK, args[1]: new rowid or PK, + /// args[2]: ...` + /// + /// Return the new rowid. + // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ? + fn insert(&mut self, args: &Values<'_>) -> Result<i64>; + /// Update: `args[0] != NULL: old rowid or PK, args[1]: new row id or PK, + /// args[2]: ...` + fn update(&mut self, args: &Values<'_>) -> Result<()>; +} + +/// Index constraint operator. +/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details. +#[derive(Debug, Eq, PartialEq)] +#[allow(non_snake_case, non_camel_case_types, missing_docs)] +#[allow(clippy::upper_case_acronyms)] +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_LIMIT, // 3.38.0 + SQLITE_INDEX_CONSTRAINT_OFFSET, // 3.38.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, + 73 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_LIMIT, + 74 => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_OFFSET, + v => IndexConstraintOp::SQLITE_INDEX_CONSTRAINT_FUNCTION(v), + } + } +} + +#[cfg(feature = "modern_sqlite")] // 3.9.0 +bitflags::bitflags! { + /// Virtual table scan flags + /// See [Function Flags](https://sqlite.org/c3ref/c_index_scan_unique.html) for details. + #[repr(C)] + pub struct IndexFlags: ::std::os::raw::c_int { + /// Default + const NONE = 0; + /// Scan visits at most 1 row. + const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE; + } +} + +/// Pass information into and receive the reply from the +/// [`VTab::best_index`] method. +/// +/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html)) +#[derive(Debug)] +pub struct IndexInfo(*mut ffi::sqlite3_index_info); + +impl IndexInfo { + /// Iterate on index constraint and its associated usage. + #[inline] + pub fn constraints_and_usages(&mut self) -> IndexConstraintAndUsageIter<'_> { + let constraints = + unsafe { slice::from_raw_parts((*self.0).aConstraint, (*self.0).nConstraint as usize) }; + let constraint_usages = unsafe { + slice::from_raw_parts_mut((*self.0).aConstraintUsage, (*self.0).nConstraint as usize) + }; + IndexConstraintAndUsageIter { + iter: constraints.iter().zip(constraint_usages.iter_mut()), + } + } + + /// Record WHERE clause constraints. + #[inline] + #[must_use] + 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. + #[inline] + #[must_use] + 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 + #[inline] + #[must_use] + pub fn num_of_order_by(&self) -> usize { + unsafe { (*self.0).nOrderBy as usize } + } + + /// Information about what parameters to pass to [`VTabCursor::filter`]. + #[inline] + 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 + #[inline] + pub fn set_idx_num(&mut self, idx_num: c_int) { + unsafe { + (*self.0).idxNum = idx_num; + } + } + + /// String used to identify the index + pub fn set_idx_str(&mut self, idx_str: &str) { + unsafe { + (*self.0).idxStr = alloc(idx_str); + (*self.0).needToFreeIdxStr = 1; + } + } + + /// True if output is already ordered + #[inline] + 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 + #[inline] + 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 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + #[inline] + pub fn set_estimated_rows(&mut self, estimated_rows: i64) { + unsafe { + (*self.0).estimatedRows = estimated_rows; + } + } + + /// Mask of SQLITE_INDEX_SCAN_* flags. + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.9.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + #[inline] + pub fn set_idx_flags(&mut self, flags: IndexFlags) { + unsafe { (*self.0).idxFlags = flags.bits() }; + } + + /// Mask of columns used by statement + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.10.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + #[inline] + pub fn col_used(&self) -> u64 { + unsafe { (*self.0).colUsed } + } + + /// Determine the collation for a virtual table constraint + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.22.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn collation(&self, constraint_idx: usize) -> Result<&str> { + use std::ffi::CStr; + let idx = constraint_idx as c_int; + let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) }; + if collation.is_null() { + return Err(Error::SqliteFailure( + ffi::Error::new(ffi::SQLITE_MISUSE), + Some(format!("{} is out of range", constraint_idx)), + )); + } + Ok(unsafe { CStr::from_ptr(collation) }.to_str()?) + } + + /*/// Determine if a virtual table query is DISTINCT + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn distinct(&self) -> c_int { + unsafe { ffi::sqlite3_vtab_distinct(self.0) } + } + + /// Constraint values + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn set_rhs_value(&mut self, constraint_idx: c_int, value: ValueRef) -> Result<()> { + // TODO ValueRef to sqlite3_value + crate::error::check(unsafe { ffi::sqlite3_vtab_rhs_value(self.O, constraint_idx, value) }) + } + + /// Identify and handle IN constraints + #[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn set_in_constraint(&mut self, constraint_idx: c_int, b_handle: c_int) -> bool { + unsafe { ffi::sqlite3_vtab_in(self.0, constraint_idx, b_handle) != 0 } + } // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html + */ +} + +/// Iterate on index constraint and its associated usage. +pub struct IndexConstraintAndUsageIter<'a> { + iter: std::iter::Zip< + slice::Iter<'a, ffi::sqlite3_index_constraint>, + slice::IterMut<'a, ffi::sqlite3_index_constraint_usage>, + >, +} + +impl<'a> Iterator for IndexConstraintAndUsageIter<'a> { + type Item = (IndexConstraint<'a>, IndexConstraintUsage<'a>); + + #[inline] + fn next(&mut self) -> Option<(IndexConstraint<'a>, IndexConstraintUsage<'a>)> { + self.iter + .next() + .map(|raw| (IndexConstraint(raw.0), IndexConstraintUsage(raw.1))) + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +/// `feature = "vtab"` +pub struct IndexConstraintIter<'a> { + iter: slice::Iter<'a, ffi::sqlite3_index_constraint>, +} + +impl<'a> Iterator for IndexConstraintIter<'a> { + type Item = IndexConstraint<'a>; + + #[inline] + fn next(&mut self) -> Option<IndexConstraint<'a>> { + self.iter.next().map(IndexConstraint) + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +/// WHERE clause constraint. +pub struct IndexConstraint<'a>(&'a ffi::sqlite3_index_constraint); + +impl IndexConstraint<'_> { + /// Column constrained. -1 for ROWID + #[inline] + #[must_use] + pub fn column(&self) -> c_int { + self.0.iColumn + } + + /// Constraint operator + #[inline] + #[must_use] + pub fn operator(&self) -> IndexConstraintOp { + IndexConstraintOp::from(self.0.op) + } + + /// True if this constraint is usable + #[inline] + #[must_use] + 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 IndexConstraintUsage<'_> { + /// if `argv_index` > 0, constraint is part of argv to + /// [`VTabCursor::filter`] + #[inline] + 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 + #[inline] + 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>; + + #[inline] + fn next(&mut self) -> Option<OrderBy<'a>> { + self.iter.next().map(OrderBy) + } + + #[inline] + 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 OrderBy<'_> { + /// Column number + #[inline] + #[must_use] + pub fn column(&self) -> c_int { + self.0.iColumn + } + + /// True for DESC. False for ASC. + #[inline] + #[must_use] + pub fn is_order_by_desc(&self) -> bool { + self.0.desc != 0 + } +} + +/// Virtual table cursor trait. +/// +/// # Safety +/// +/// Implementations must be like: +/// ```rust,ignore +/// #[repr(C)] +/// struct MyTabCursor { +/// /// Base class. Must be first +/// base: rusqlite::vtab::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`](VTabCursor::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 { + /// Set current cell value + #[inline] + 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) // 3.22.0 & xColumn +} + +/// Wrapper to [`VTabCursor::filter`] arguments, the values +/// requested by [`VTab::best_index`]. +pub struct Values<'a> { + args: &'a [*mut ffi::sqlite3_value], +} + +impl Values<'_> { + /// Returns the number of values. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.args.len() + } + + /// Returns `true` if there is no value. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.args.is_empty() + } + + /// Returns value at `idx` + 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::InvalidBlobSize { .. } => { + Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(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")] + #[cfg_attr(docsrs, doc(cfg(feature = "array")))] + fn get_array(&self, idx: usize) -> 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() { + None + } else { + 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 + }) + } + } + + /// Turns `Values` into an iterator. + #[inline] + #[must_use] + pub fn iter(&self) -> ValueIter<'_> { + ValueIter { + iter: self.args.iter(), + } + } + // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html & 3.38.0 +} + +impl<'a> IntoIterator for &'a Values<'a> { + type IntoIter = ValueIter<'a>; + type Item = ValueRef<'a>; + + #[inline] + fn into_iter(self) -> ValueIter<'a> { + self.iter() + } +} + +/// [`Values`] iterator. +pub struct ValueIter<'a> { + iter: slice::Iter<'a, *mut ffi::sqlite3_value>, +} + +impl<'a> Iterator for ValueIter<'a> { + type Item = ValueRef<'a>; + + #[inline] + fn next(&mut self) -> Option<ValueRef<'a>> { + self.iter + .next() + .map(|&raw| unsafe { ValueRef::from_value(raw) }) + } + + #[inline] + 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). + #[inline] + pub fn create_module<'vtab, T: VTab<'vtab>>( + &self, + module_name: &str, + module: &'static Module<'vtab, T>, + aux: Option<T::Aux>, + ) -> Result<()> { + self.db.borrow_mut().create_module(module_name, module, aux) + } +} + +impl InnerConnection { + fn create_module<'vtab, T: VTab<'vtab>>( + &mut self, + module_name: &str, + module: &'static Module<'vtab, T>, + aux: Option<T::Aux>, + ) -> Result<()> { + use crate::version; + if version::version_number() < 3_009_000 && module.base.xCreate.is_none() { + return Err(Error::ModuleError(format!( + "Eponymous-only virtual table not supported by SQLite version {}", + version::version() + ))); + } + 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.cast::<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 occurrences by +/// doubling them (`""`). +#[must_use] +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 +#[must_use] +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], // FIXME handle inner escaped quote(s) + _ => s, + }, + _ => s, + } +} +/// The boolean can be one of: +/// ```text +/// 1 yes true on +/// 0 no false off +/// ``` +#[must_use] +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 + } +} + +/// `<param_name>=['"]?<param_value>['"]?` => `(<param_name>, <param_value>)` +pub fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> { + let arg = std::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))) +} + +// FIXME copy/paste from function.rs +unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { + drop(Box::from_raw(p.cast::<T>())); +} + +unsafe extern "C" fn rust_create<'vtab, 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<'vtab>, +{ + use std::ffi::CStr; + + let mut conn = VTabConnection(db); + let aux = aux.cast::<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.cast::<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<'vtab, 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<'vtab>, +{ + use std::ffi::CStr; + + let mut conn = VTabConnection(db); + let aux = aux.cast::<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.cast::<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<'vtab, T>( + vtab: *mut ffi::sqlite3_vtab, + info: *mut ffi::sqlite3_index_info, +) -> c_int +where + T: VTab<'vtab>, +{ + let vt = vtab.cast::<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<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +where + T: VTab<'vtab>, +{ + if vtab.is_null() { + return ffi::SQLITE_OK; + } + let vtab = vtab.cast::<T>(); + drop(Box::from_raw(vtab)); + ffi::SQLITE_OK +} + +unsafe extern "C" fn rust_destroy<'vtab, T>(vtab: *mut ffi::sqlite3_vtab) -> c_int +where + T: CreateVTab<'vtab>, +{ + if vtab.is_null() { + return ffi::SQLITE_OK; + } + let vt = vtab.cast::<T>(); + match (*vt).destroy() { + Ok(_) => { + drop(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<'vtab, T: 'vtab>( + vtab: *mut ffi::sqlite3_vtab, + pp_cursor: *mut *mut ffi::sqlite3_vtab_cursor, +) -> c_int +where + T: VTab<'vtab>, +{ + let vt = vtab.cast::<T>(); + match (*vt).open() { + Ok(cursor) => { + let boxed_cursor: *mut T::Cursor = Box::into_raw(Box::new(cursor)); + *pp_cursor = boxed_cursor.cast::<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.cast::<C>(); + drop(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.cast::<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.cast::<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.cast::<C>(); + match (*cr).rowid() { + Ok(rowid) => { + *p_rowid = rowid; + ffi::SQLITE_OK + } + err => cursor_error(cursor, err), + } +} + +unsafe extern "C" fn rust_update<'vtab, T: 'vtab>( + vtab: *mut ffi::sqlite3_vtab, + argc: c_int, + argv: *mut *mut ffi::sqlite3_value, + p_rowid: *mut ffi::sqlite3_int64, +) -> c_int +where + T: UpdateVTab<'vtab>, +{ + assert!(argc >= 1); + let args = slice::from_raw_parts_mut(argv, argc as usize); + let vt = vtab.cast::<T>(); + let r = if args.len() == 1 { + (*vt).delete(ValueRef::from_value(args[0])) + } else if ffi::sqlite3_value_type(args[0]) == ffi::SQLITE_NULL { + // TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ? + let values = Values { args }; + match (*vt).insert(&values) { + Ok(rowid) => { + *p_rowid = rowid; + Ok(()) + } + Err(e) => Err(e), + } + } else { + let values = Values { args }; + (*vt).update(&values) + }; + match r { + 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 + } + } +} + +/// Virtual table cursors can set an error message by assigning a string to +/// `zErrMsg`. +#[cold] +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`. +#[cold] +unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) { + if !(*vtab).zErrMsg.is_null() { + ffi::sqlite3_free((*vtab).zErrMsg.cast::<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. +#[cold] +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")] +#[cfg_attr(docsrs, doc(cfg(feature = "array")))] +pub mod array; +#[cfg(feature = "csvtab")] +#[cfg_attr(docsrs, doc(cfg(feature = "csvtab")))] +pub mod csvtab; +#[cfg(feature = "series")] +#[cfg_attr(docsrs, doc(cfg(feature = "series")))] +pub mod series; // SQLite >= 3.9.0 +#[cfg(test)] +mod vtablog; + +#[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")); + } +} |