summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rusqlite/src/vtab/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/rusqlite/src/vtab/mod.rs')
-rw-r--r--third_party/rust/rusqlite/src/vtab/mod.rs1366
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"));
+ }
+}