diff --git a/src/config.rs b/src/config.rs index b59e5ef..b295d97 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,7 +33,7 @@ pub enum DbConfig { SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0 /// Activates or deactivates the "reset" flag for a database connection. /// Run VACUUM with this flag set to reset the database. - SQLITE_DBCONFIG_RESET_DATABASE = 1009, + SQLITE_DBCONFIG_RESET_DATABASE = 1009, // 3.24.0 /// Activates or deactivates the "defensive" flag for a database connection. SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0 /// Activates or deactivates the "writable_schema" flag. diff --git a/src/context.rs b/src/context.rs index 5f935fa..f2413e7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -23,6 +23,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput< #[cfg(feature = "blob")] ToSqlOutput::ZeroBlob(len) => { + // TODO sqlite3_result_zeroblob64 // 3.8.11 return ffi::sqlite3_result_zeroblob(ctx, len); } #[cfg(feature = "array")] @@ -50,6 +51,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput< // TODO sqlite3_result_error Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE), }; + // TODO sqlite3_result_text64 // 3.8.7 ffi::sqlite3_result_text(ctx, c_str, len, destructor); } } @@ -60,6 +62,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput< } else if length == 0 { ffi::sqlite3_result_zeroblob(ctx, 0); } else { + // TODO sqlite3_result_blob64 // 3.8.7 ffi::sqlite3_result_blob( ctx, b.as_ptr().cast::(), diff --git a/src/error.rs b/src/error.rs index 129f697..7f1087e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -339,6 +339,7 @@ impl error::Error for Error { #[cold] pub fn error_from_sqlite_code(code: c_int, message: Option) -> Error { + // TODO sqlite3_error_offset // 3.38.0, #1130 Error::SqliteFailure(ffi::Error::new(code), message) } diff --git a/src/functions.rs b/src/functions.rs index e613182..6de9612 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -162,6 +162,19 @@ impl Context<'_> { unsafe { ValueRef::from_value(arg) } } + /// Returns the subtype of `idx`th argument. + /// + /// # Failure + /// + /// Will panic if `idx` is greater than or equal to + /// [`self.len()`](Context::len). + #[cfg(feature = "modern_sqlite")] // 3.9.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn get_subtype(&self, idx: usize) -> std::os::raw::c_uint { + let arg = self.args[idx]; + unsafe { ffi::sqlite3_value_subtype(arg) } + } + /// Fetch or insert the auxiliary data associated with a particular /// parameter. This is intended to be an easier-to-use way of fetching it /// compared to calling [`get_aux`](Context::get_aux) and @@ -234,6 +247,13 @@ impl Context<'_> { phantom: PhantomData, }) } + + /// Set the Subtype of an SQL function + #[cfg(feature = "modern_sqlite")] // 3.9.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn set_result_subtype(&self, sub_type: std::os::raw::c_uint) { + unsafe { ffi::sqlite3_result_subtype(self.ctx, sub_type) }; + } } /// A reference to a connection handle with a lifetime bound to something. @@ -319,7 +339,7 @@ bitflags::bitflags! { /// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters. const SQLITE_UTF16 = ffi::SQLITE_UTF16; /// Means that the function always gives the same output when the input parameters are the same. - const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC; + const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC; // 3.8.3 /// Means that the function may only be invoked from top-level SQL. const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0 /// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the sub-types of its arguments. diff --git a/src/hooks.rs b/src/hooks.rs index f0ae1f3..7a3347d 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -285,7 +285,7 @@ impl<'c> AuthAction<'c> { operation: TransactionOperation::from_str(operation_str), savepoint_name, }, - #[cfg(feature = "modern_sqlite")] + #[cfg(feature = "modern_sqlite")] // 3.8.3 (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive, (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 }, } diff --git a/src/inner_connection.rs b/src/inner_connection.rs index 0ea630e..e7bfc94 100644 --- a/src/inner_connection.rs +++ b/src/inner_connection.rs @@ -222,6 +222,7 @@ impl InnerConnection { let mut c_stmt = ptr::null_mut(); let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?; let mut c_tail = ptr::null(); + // TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728 #[cfg(not(feature = "unlock_notify"))] let r = unsafe { ffi::sqlite3_prepare_v2( @@ -277,7 +278,14 @@ impl InnerConnection { #[inline] pub fn changes(&self) -> usize { - unsafe { ffi::sqlite3_changes(self.db()) as usize } + #[cfg(not(feature = "modern_sqlite"))] + unsafe { + ffi::sqlite3_changes(self.db()) as usize + } + #[cfg(feature = "modern_sqlite")] // 3.37.0 + unsafe { + ffi::sqlite3_changes64(self.db()) as usize + } } #[inline] @@ -308,6 +316,50 @@ impl InnerConnection { #[cfg(not(feature = "hooks"))] #[inline] fn remove_hooks(&mut self) {} + + #[cfg(feature = "modern_sqlite")] // 3.7.11 + pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result { + let name = db_name.as_cstring()?; + let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) }; + match r { + 0 => Ok(false), + 1 => Ok(true), + -1 => Err(Error::SqliteFailure( + ffi::Error::new(ffi::SQLITE_MISUSE), + Some(format!("{:?} is not the name of a database", db_name)), + )), + _ => Err(error_from_sqlite_code( + r, + Some("Unexpected result".to_owned()), + )), + } + } + + #[cfg(feature = "modern_sqlite")] // 3.37.0 + pub fn txn_state( + &self, + db_name: Option>, + ) -> Result { + let r = if let Some(ref name) = db_name { + let name = name.as_cstring()?; + unsafe { ffi::sqlite3_txn_state(self.db, name.as_ptr()) } + } else { + unsafe { ffi::sqlite3_txn_state(self.db, ptr::null()) } + }; + match r { + 0 => Ok(super::transaction::TransactionState::None), + 1 => Ok(super::transaction::TransactionState::Read), + 2 => Ok(super::transaction::TransactionState::Write), + -1 => Err(Error::SqliteFailure( + ffi::Error::new(ffi::SQLITE_MISUSE), + Some(format!("{:?} is not the name of a valid schema", db_name)), + )), + _ => Err(error_from_sqlite_code( + r, + Some("Unexpected result".to_owned()), + )), + } + } } impl Drop for InnerConnection { diff --git a/src/lib.rs b/src/lib.rs index ce0e98e..43e9ad9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -927,6 +927,13 @@ impl Connection { pub fn cache_flush(&self) -> Result<()> { self.db.borrow_mut().cache_flush() } + + /// Determine if a database is read-only + #[cfg(feature = "modern_sqlite")] // 3.7.11 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result { + self.db.borrow().db_readonly(db_name) + } } impl fmt::Debug for Connection { @@ -1024,9 +1031,9 @@ bitflags::bitflags! { const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000; /// The database is opened shared cache disabled. const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000; - /// The database filename is not allowed to be a symbolic link. + /// The database filename is not allowed to be a symbolic link. (3.31.0) const SQLITE_OPEN_NOFOLLOW = 0x0100_0000; - /// Extended result codes. + /// Extended result codes. (3.37.0) const SQLITE_OPEN_EXRESCODE = 0x0200_0000; } } @@ -2012,4 +2019,12 @@ mod test { let db = Connection::open_in_memory()?; db.cache_flush() } + + #[test] + #[cfg(feature = "modern_sqlite")] + pub fn db_readonly() -> Result<()> { + let db = Connection::open_in_memory()?; + assert!(!db.is_readonly(super::MAIN_DB)?); + Ok(()) + } } diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 8e624dc..5af3a8c 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -224,6 +224,14 @@ impl RawStatement { pub fn tail(&self) -> usize { self.tail } + + #[inline] + #[cfg(feature = "modern_sqlite")] // 3.28.0 + pub fn is_explain(&self) -> i32 { + unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) } + } + + // TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE } impl Drop for RawStatement { diff --git a/src/statement.rs b/src/statement.rs index 9a84cbd..bb3bf73 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -727,6 +727,7 @@ impl Statement<'_> { #[cfg(feature = "blob")] ToSqlOutput::ZeroBlob(len) => { + // TODO sqlite3_bind_zeroblob64 // 3.8.11 return self .conn .decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) }); @@ -750,6 +751,7 @@ impl Statement<'_> { ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) }, ValueRef::Text(s) => unsafe { let (c_str, len, destructor) = str_for_sqlite(s)?; + // TODO sqlite3_bind_text64 // 3.8.7 ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor) }, ValueRef::Blob(b) => unsafe { @@ -757,6 +759,7 @@ impl Statement<'_> { if length == 0 { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0) } else { + // TODO sqlite3_bind_blob64 // 3.8.7 ffi::sqlite3_bind_blob( ptr, col as c_int, @@ -838,6 +841,16 @@ impl Statement<'_> { self.stmt.get_status(status, true) } + /// Returns 1 if the prepared statement is an EXPLAIN statement, + /// or 2 if the statement is an EXPLAIN QUERY PLAN, + /// or 0 if it is an ordinary statement or a NULL pointer. + #[inline] + #[cfg(feature = "modern_sqlite")] // 3.28.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn is_explain(&self) -> i32 { + self.stmt.is_explain() + } + #[cfg(feature = "extra_check")] #[inline] pub(crate) fn check_no_tail(&self) -> Result<()> { @@ -985,15 +998,15 @@ pub enum StatementStatus { AutoIndex = 3, /// Equivalent to SQLITE_STMTSTATUS_VM_STEP VmStep = 4, - /// Equivalent to SQLITE_STMTSTATUS_REPREPARE + /// Equivalent to SQLITE_STMTSTATUS_REPREPARE (3.20.0) RePrepare = 5, - /// Equivalent to SQLITE_STMTSTATUS_RUN + /// Equivalent to SQLITE_STMTSTATUS_RUN (3.20.0) Run = 6, /// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS FilterMiss = 7, /// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT FilterHit = 8, - /// Equivalent to SQLITE_STMTSTATUS_MEMUSED + /// Equivalent to SQLITE_STMTSTATUS_MEMUSED (3.20.0) MemUsed = 99, } @@ -1508,4 +1521,13 @@ mod test { assert_eq!(expected, actual); Ok(()) } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn is_explain() -> Result<()> { + let db = Connection::open_in_memory()?; + let stmt = db.prepare("SELECT 1;")?; + assert_eq!(0, stmt.is_explain()); + Ok(()) + } } diff --git a/src/trace.rs b/src/trace.rs index 3932976..7fc9090 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -119,6 +119,8 @@ impl Connection { None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) }, }; } + + // TODO sqlite3_trace_v2 (https://sqlite.org/c3ref/trace_v2.html) // 3.14.0, #977 } #[cfg(test)] diff --git a/src/transaction.rs b/src/transaction.rs index 296b2aa..3afbe1a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -375,6 +375,20 @@ impl Drop for Savepoint<'_> { } } +/// Transaction state of a database +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +#[cfg(feature = "modern_sqlite")] // 3.37.0 +#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] +pub enum TransactionState { + /// Equivalent to SQLITE_TXN_NONE + None, + /// Equivalent to SQLITE_TXN_READ + Read, + /// Equivalent to SQLITE_TXN_WRITE + Write, +} + impl Connection { /// Begin a new transaction with the default behavior (DEFERRED). /// @@ -499,6 +513,16 @@ impl Connection { pub fn savepoint_with_name>(&mut self, name: T) -> Result> { Savepoint::with_name(self, name) } + + /// Determine the transaction state of a database + #[cfg(feature = "modern_sqlite")] // 3.37.0 + #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] + pub fn transaction_state( + &self, + db_name: Option>, + ) -> Result { + self.db.borrow().txn_state(db_name) + } } #[cfg(test)] @@ -710,4 +734,25 @@ mod test { assert_eq!(x, i); Ok(()) } + + #[test] + #[cfg(feature = "modern_sqlite")] + fn txn_state() -> Result<()> { + use super::TransactionState; + use crate::DatabaseName; + let db = Connection::open_in_memory()?; + assert_eq!( + TransactionState::None, + db.transaction_state(Some(DatabaseName::Main))? + ); + assert_eq!(TransactionState::None, db.transaction_state(None)?); + db.execute_batch("BEGIN")?; + assert_eq!(TransactionState::None, db.transaction_state(None)?); + let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?; + assert_eq!(TransactionState::Read, db.transaction_state(None)?); + db.pragma_update(None, "user_version", 1)?; + assert_eq!(TransactionState::Write, db.transaction_state(None)?); + db.execute_batch("ROLLBACK")?; + Ok(()) + } } diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index c0d81ca..12806f8 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -257,4 +257,7 @@ impl<'a> ValueRef<'a> { _ => unreachable!("sqlite3_value_type returned invalid value"), } } + + // TODO sqlite3_value_nochange // 3.22.0 & VTab xUpdate + // TODO sqlite3_value_frombind // 3.28.0 } diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index df3529a..831c466 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -31,7 +31,7 @@ use crate::ffi; use crate::types::Null; use crate::vtab::{ dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, - VTab, VTabConnection, VTabCursor, Values, + VTab, VTabConfig, VTabConnection, VTabCursor, Values, }; use crate::{Connection, Error, Result}; @@ -101,7 +101,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab { type Cursor = CsvTabCursor<'vtab>; fn connect( - _: &mut VTabConnection, + db: &mut VTabConnection, _aux: Option<&()>, args: &[&[u8]], ) -> Result<(String, CsvTab)> { @@ -249,7 +249,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab { } schema = Some(sql); } - + db.config(VTabConfig::DirectOnly)?; Ok((schema.unwrap(), vtab)) } diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs index bdb6509..da3176a 100644 --- a/src/vtab/mod.rs +++ b/src/vtab/mod.rs @@ -165,13 +165,32 @@ pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, } } +/// Virtual table configuration options +#[repr(i32)] +#[non_exhaustive] +#[cfg(feature = "modern_sqlite")] // 3.7.7 +#[derive(Debug, Clone, Copy, 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. /// @@ -310,10 +329,24 @@ impl From 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 +409,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 +443,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 +674,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 +742,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> { diff --git a/src/vtab/series.rs b/src/vtab/series.rs index f26212a..2dec476 100644 --- a/src/vtab/series.rs +++ b/src/vtab/series.rs @@ -10,8 +10,8 @@ use std::os::raw::c_int; use crate::ffi; use crate::types::Type; use crate::vtab::{ - eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor, - Values, + eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection, + VTabCursor, Values, }; use crate::{Connection, Error, Result}; @@ -57,13 +57,14 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab { type Cursor = SeriesTabCursor<'vtab>; fn connect( - _: &mut VTabConnection, + db: &mut VTabConnection, _aux: Option<&()>, _args: &[&[u8]], ) -> Result<(String, SeriesTab)> { let vtab = SeriesTab { base: ffi::sqlite3_vtab::default(), }; + db.config(VTabConfig::Innocuous)?; Ok(( "CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(), vtab, @@ -103,6 +104,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab { let mut constraint_usage = info.constraint_usage(*j); constraint_usage.set_argv_index(n_arg); constraint_usage.set_omit(true); + #[cfg(all(test, feature = "modern_sqlite"))] + debug_assert_eq!(Ok("BINARY"), info.collation(*j)); } if !(unusable_mask & !idx_num).is_empty() { return Err(Error::SqliteFailure(