diff options
Diffstat (limited to 'src/vtab/mod.rs')
-rw-r--r-- | src/vtab/mod.rs | 336 |
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 { |