Add some missing wrappers (#1139)

* Add some missing wrappers:

sqlite3_value_subtype
sqlite3_result_subtype
sqlite3_changes64
sqlite3_db_readonly
sqlite3_txn_state
sqlite3_stmt_isexplain
sqlite3_vtab_config
sqlite3_index_info.idxFlags
sqlite3_index_info.colUsed
sqlite3_index_info.idxStr
sqlite3_vtab_collation

* Mark series VTab as innocuous and csv as direct only
This commit is contained in:
gwenn 2022-03-17 19:58:02 +01:00 committed by GitHub
parent 2afbdeeb52
commit 5e2c103a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 287 additions and 21 deletions

View File

@ -33,7 +33,7 @@ pub enum DbConfig {
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0 SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
/// Activates or deactivates the "reset" flag for a database connection. /// Activates or deactivates the "reset" flag for a database connection.
/// Run VACUUM with this flag set to reset the database. /// 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. /// Activates or deactivates the "defensive" flag for a database connection.
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0 SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
/// Activates or deactivates the "writable_schema" flag. /// Activates or deactivates the "writable_schema" flag.

View File

@ -23,6 +23,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => { ToSqlOutput::ZeroBlob(len) => {
// TODO sqlite3_result_zeroblob64 // 3.8.11
return ffi::sqlite3_result_zeroblob(ctx, len); return ffi::sqlite3_result_zeroblob(ctx, len);
} }
#[cfg(feature = "array")] #[cfg(feature = "array")]
@ -50,6 +51,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
// TODO sqlite3_result_error // TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE), 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); 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 { } else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0); ffi::sqlite3_result_zeroblob(ctx, 0);
} else { } else {
// TODO sqlite3_result_blob64 // 3.8.7
ffi::sqlite3_result_blob( ffi::sqlite3_result_blob(
ctx, ctx,
b.as_ptr().cast::<c_void>(), b.as_ptr().cast::<c_void>(),

View File

@ -339,6 +339,7 @@ impl error::Error for Error {
#[cold] #[cold]
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error { pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
// TODO sqlite3_error_offset // 3.38.0, #1130
Error::SqliteFailure(ffi::Error::new(code), message) Error::SqliteFailure(ffi::Error::new(code), message)
} }

View File

@ -162,6 +162,19 @@ impl Context<'_> {
unsafe { ValueRef::from_value(arg) } 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 /// Fetch or insert the auxiliary data associated with a particular
/// parameter. This is intended to be an easier-to-use way of fetching it /// parameter. This is intended to be an easier-to-use way of fetching it
/// compared to calling [`get_aux`](Context::get_aux) and /// compared to calling [`get_aux`](Context::get_aux) and
@ -234,6 +247,13 @@ impl Context<'_> {
phantom: PhantomData, 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. /// 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. /// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters.
const SQLITE_UTF16 = ffi::SQLITE_UTF16; const SQLITE_UTF16 = ffi::SQLITE_UTF16;
/// Means that the function always gives the same output when the input parameters are the same. /// 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. /// Means that the function may only be invoked from top-level SQL.
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0 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. /// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the sub-types of its arguments.

View File

@ -285,7 +285,7 @@ impl<'c> AuthAction<'c> {
operation: TransactionOperation::from_str(operation_str), operation: TransactionOperation::from_str(operation_str),
savepoint_name, savepoint_name,
}, },
#[cfg(feature = "modern_sqlite")] #[cfg(feature = "modern_sqlite")] // 3.8.3
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive, (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 }, (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
} }

View File

@ -222,6 +222,7 @@ impl InnerConnection {
let mut c_stmt = ptr::null_mut(); let mut c_stmt = ptr::null_mut();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?; let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null(); 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"))] #[cfg(not(feature = "unlock_notify"))]
let r = unsafe { let r = unsafe {
ffi::sqlite3_prepare_v2( ffi::sqlite3_prepare_v2(
@ -277,7 +278,14 @@ impl InnerConnection {
#[inline] #[inline]
pub fn changes(&self) -> usize { 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] #[inline]
@ -308,6 +316,50 @@ impl InnerConnection {
#[cfg(not(feature = "hooks"))] #[cfg(not(feature = "hooks"))]
#[inline] #[inline]
fn remove_hooks(&mut self) {} fn remove_hooks(&mut self) {}
#[cfg(feature = "modern_sqlite")] // 3.7.11
pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result<bool> {
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<super::DatabaseName<'_>>,
) -> Result<super::transaction::TransactionState> {
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 { impl Drop for InnerConnection {

View File

@ -927,6 +927,13 @@ impl Connection {
pub fn cache_flush(&self) -> Result<()> { pub fn cache_flush(&self) -> Result<()> {
self.db.borrow_mut().cache_flush() 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<bool> {
self.db.borrow().db_readonly(db_name)
}
} }
impl fmt::Debug for Connection { impl fmt::Debug for Connection {
@ -1024,9 +1031,9 @@ bitflags::bitflags! {
const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000; const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
/// The database is opened shared cache disabled. /// The database is opened shared cache disabled.
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000; 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; const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
/// Extended result codes. /// Extended result codes. (3.37.0)
const SQLITE_OPEN_EXRESCODE = 0x0200_0000; const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
} }
} }
@ -2012,4 +2019,12 @@ mod test {
let db = Connection::open_in_memory()?; let db = Connection::open_in_memory()?;
db.cache_flush() 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(())
}
} }

View File

@ -224,6 +224,14 @@ impl RawStatement {
pub fn tail(&self) -> usize { pub fn tail(&self) -> usize {
self.tail 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 { impl Drop for RawStatement {

View File

@ -727,6 +727,7 @@ impl Statement<'_> {
#[cfg(feature = "blob")] #[cfg(feature = "blob")]
ToSqlOutput::ZeroBlob(len) => { ToSqlOutput::ZeroBlob(len) => {
// TODO sqlite3_bind_zeroblob64 // 3.8.11
return self return self
.conn .conn
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) }); .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::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ValueRef::Text(s) => unsafe { ValueRef::Text(s) => unsafe {
let (c_str, len, destructor) = str_for_sqlite(s)?; 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) ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
}, },
ValueRef::Blob(b) => unsafe { ValueRef::Blob(b) => unsafe {
@ -757,6 +759,7 @@ impl Statement<'_> {
if length == 0 { if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0) ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else { } else {
// TODO sqlite3_bind_blob64 // 3.8.7
ffi::sqlite3_bind_blob( ffi::sqlite3_bind_blob(
ptr, ptr,
col as c_int, col as c_int,
@ -838,6 +841,16 @@ impl Statement<'_> {
self.stmt.get_status(status, true) 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")] #[cfg(feature = "extra_check")]
#[inline] #[inline]
pub(crate) fn check_no_tail(&self) -> Result<()> { pub(crate) fn check_no_tail(&self) -> Result<()> {
@ -985,15 +998,15 @@ pub enum StatementStatus {
AutoIndex = 3, AutoIndex = 3,
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP /// Equivalent to SQLITE_STMTSTATUS_VM_STEP
VmStep = 4, VmStep = 4,
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE /// Equivalent to SQLITE_STMTSTATUS_REPREPARE (3.20.0)
RePrepare = 5, RePrepare = 5,
/// Equivalent to SQLITE_STMTSTATUS_RUN /// Equivalent to SQLITE_STMTSTATUS_RUN (3.20.0)
Run = 6, Run = 6,
/// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS /// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
FilterMiss = 7, FilterMiss = 7,
/// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT /// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
FilterHit = 8, FilterHit = 8,
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED /// Equivalent to SQLITE_STMTSTATUS_MEMUSED (3.20.0)
MemUsed = 99, MemUsed = 99,
} }
@ -1508,4 +1521,13 @@ mod test {
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) 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(())
}
} }

View File

@ -119,6 +119,8 @@ impl Connection {
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) }, 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)] #[cfg(test)]

View File

@ -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 { impl Connection {
/// Begin a new transaction with the default behavior (DEFERRED). /// Begin a new transaction with the default behavior (DEFERRED).
/// ///
@ -499,6 +513,16 @@ impl Connection {
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name(self, name) 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<crate::DatabaseName<'_>>,
) -> Result<TransactionState> {
self.db.borrow().txn_state(db_name)
}
} }
#[cfg(test)] #[cfg(test)]
@ -710,4 +734,25 @@ mod test {
assert_eq!(x, i); assert_eq!(x, i);
Ok(()) 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(())
}
} }

View File

@ -257,4 +257,7 @@ impl<'a> ValueRef<'a> {
_ => unreachable!("sqlite3_value_type returned invalid value"), _ => unreachable!("sqlite3_value_type returned invalid value"),
} }
} }
// TODO sqlite3_value_nochange // 3.22.0 & VTab xUpdate
// TODO sqlite3_value_frombind // 3.28.0
} }

View File

@ -31,7 +31,7 @@ use crate::ffi;
use crate::types::Null; use crate::types::Null;
use crate::vtab::{ use crate::vtab::{
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, 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}; use crate::{Connection, Error, Result};
@ -101,7 +101,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
type Cursor = CsvTabCursor<'vtab>; type Cursor = CsvTabCursor<'vtab>;
fn connect( fn connect(
_: &mut VTabConnection, db: &mut VTabConnection,
_aux: Option<&()>, _aux: Option<&()>,
args: &[&[u8]], args: &[&[u8]],
) -> Result<(String, CsvTab)> { ) -> Result<(String, CsvTab)> {
@ -249,7 +249,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
} }
schema = Some(sql); schema = Some(sql);
} }
db.config(VTabConfig::DirectOnly)?;
Ok((schema.unwrap(), vtab)) Ok((schema.unwrap(), vtab))
} }

View File

@ -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"` /// `feature = "vtab"`
pub struct VTabConnection(*mut ffi::sqlite3); pub struct VTabConnection(*mut ffi::sqlite3);
impl VTabConnection { 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. /// Get access to the underlying SQLite database connection handle.
/// ///
@ -310,10 +329,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 /// Pass information into and receive the reply from the
/// [`VTab::best_index`] method. /// [`VTab::best_index`] method.
/// ///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html)) /// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
#[derive(Debug)]
pub struct IndexInfo(*mut ffi::sqlite3_index_info); pub struct IndexInfo(*mut ffi::sqlite3_index_info);
impl IndexInfo { 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 /// True if output is already ordered
#[inline] #[inline]
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) { pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
@ -402,10 +443,60 @@ impl IndexInfo {
} }
} }
// TODO idxFlags /// Mask of SQLITE_INDEX_SCAN_* flags.
// TODO colUsed #[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. /// Iterate on index constraint and its associated usage.
@ -583,7 +674,7 @@ impl Context {
Ok(()) 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 /// Wrapper to [`VTabCursor::filter`] arguments, the values
@ -651,6 +742,7 @@ impl Values<'_> {
iter: self.args.iter(), 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> { impl<'a> IntoIterator for &'a Values<'a> {

View File

@ -10,8 +10,8 @@ use std::os::raw::c_int;
use crate::ffi; use crate::ffi;
use crate::types::Type; use crate::types::Type;
use crate::vtab::{ use crate::vtab::{
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor, eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection,
Values, VTabCursor, Values,
}; };
use crate::{Connection, Error, Result}; use crate::{Connection, Error, Result};
@ -57,13 +57,14 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
type Cursor = SeriesTabCursor<'vtab>; type Cursor = SeriesTabCursor<'vtab>;
fn connect( fn connect(
_: &mut VTabConnection, db: &mut VTabConnection,
_aux: Option<&()>, _aux: Option<&()>,
_args: &[&[u8]], _args: &[&[u8]],
) -> Result<(String, SeriesTab)> { ) -> Result<(String, SeriesTab)> {
let vtab = SeriesTab { let vtab = SeriesTab {
base: ffi::sqlite3_vtab::default(), base: ffi::sqlite3_vtab::default(),
}; };
db.config(VTabConfig::Innocuous)?;
Ok(( Ok((
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(), "CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
vtab, vtab,
@ -103,6 +104,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
let mut constraint_usage = info.constraint_usage(*j); let mut constraint_usage = info.constraint_usage(*j);
constraint_usage.set_argv_index(n_arg); constraint_usage.set_argv_index(n_arg);
constraint_usage.set_omit(true); 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() { if !(unusable_mask & !idx_num).is_empty() {
return Err(Error::SqliteFailure( return Err(Error::SqliteFailure(