aboutsummaryrefslogtreecommitdiff
path: root/src/vtab/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/vtab/mod.rs')
-rw-r--r--src/vtab/mod.rs336
1 files changed, 267 insertions, 69 deletions
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index bdb6509..07008f3 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -57,6 +57,23 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// 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))
@@ -84,31 +101,26 @@ const ZERO_MODULE: ffi::sqlite3_module = unsafe {
.module
};
-/// 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> {
- // 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.
+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, // 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,
+ 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,
@@ -120,7 +132,46 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
xRollbackTo: None,
..ZERO_MODULE
},
- phantom: PhantomData::<&'vtab T>,
+ 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)
+ }
}
}
@@ -129,49 +180,36 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
/// 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> {
- // 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
- #[allow(clippy::needless_update)]
- &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::<&'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 {
- // TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
+ /// 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)
+ // TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html) & xUpdate
/// Get access to the underlying SQLite database connection handle.
///
@@ -191,7 +229,7 @@ impl VTabConnection {
}
}
-/// Virtual table instance trait.
+/// Eponymous-only virtual table instance trait.
///
/// # Safety
///
@@ -229,13 +267,17 @@ pub unsafe trait VTab<'vtab>: Sized {
/// Create a new cursor used for accessing a virtual table.
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
- fn open(&'vtab self) -> Result<Self::Cursor>;
+ fn open(&'vtab mut self) -> Result<Self::Cursor>;
}
-/// Non-eponymous virtual table instance trait.
+/// 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
@@ -261,9 +303,26 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
}
}
+/// 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, PartialEq)]
+#[derive(Debug, Eq, PartialEq)]
#[allow(non_snake_case, non_camel_case_types, missing_docs)]
#[allow(clippy::upper_case_acronyms)]
pub enum IndexConstraintOp {
@@ -310,10 +369,24 @@ impl From<u8> for IndexConstraintOp {
}
}
+#[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 {
@@ -376,6 +449,14 @@ impl IndexInfo {
}
}
+ /// 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) {
@@ -402,10 +483,60 @@ impl IndexInfo {
}
}
- // TODO idxFlags
- // TODO colUsed
+ /// 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() };
+ }
- // TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
+ /// 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.
@@ -583,7 +714,7 @@ impl Context {
Ok(())
}
- // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
+ // TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html) // 3.22.0 & xColumn
}
/// Wrapper to [`VTabCursor::filter`] arguments, the values
@@ -651,6 +782,7 @@ impl Values<'_> {
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> {
@@ -707,6 +839,13 @@ impl InnerConnection {
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) => {
@@ -754,7 +893,7 @@ pub fn dequote(s: &str) -> &str {
}
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],
+ Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s)
_ => s,
},
_ => s,
@@ -784,6 +923,20 @@ pub fn parse_boolean(s: &str) -> Option<bool> {
}
}
+/// `<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>()));
@@ -810,7 +963,7 @@ where
.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((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 {
@@ -862,7 +1015,7 @@ where
.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((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 {
@@ -1061,6 +1214,49 @@ where
}
}
+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]
@@ -1138,6 +1334,8 @@ 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 {