aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid LeGare <legare@google.com>2022-03-02 21:26:14 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-03-02 21:26:14 +0000
commit1f78690480e043c9a3bcc5ce5b11778280ddf3ad (patch)
treeed3dae2c91a4d67fe43675ec1ee9eaf437cd8280 /src
parent8a0e0cefcdd7fd8f911691e4422d8e58311cd132 (diff)
parent93c8cc61e4753e8db9b57fbc2e3e9274495e9809 (diff)
downloadrusqlite-1f78690480e043c9a3bcc5ce5b11778280ddf3ad.tar.gz
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/rusqlite/+/2006011 Change-Id: I51755a378ec473338367ada10f64c2fa4c642863
Diffstat (limited to 'src')
-rw-r--r--src/backup.rs29
-rw-r--r--src/blob/mod.rs18
-rw-r--r--src/blob/pos_io.rs23
-rw-r--r--src/busy.rs4
-rw-r--r--src/cache.rs11
-rw-r--r--src/collation.rs26
-rw-r--r--src/column.rs82
-rw-r--r--src/config.rs49
-rw-r--r--src/context.rs4
-rw-r--r--src/error.rs29
-rw-r--r--src/functions.rs113
-rw-r--r--src/hooks.rs564
-rw-r--r--src/inner_connection.rs160
-rw-r--r--src/lib.rs275
-rw-r--r--src/limits.rs110
-rw-r--r--src/load_extension_guard.rs20
-rw-r--r--src/params.rs10
-rw-r--r--src/pragma.rs46
-rw-r--r--src/raw_statement.rs45
-rw-r--r--src/row.rs46
-rw-r--r--src/session.rs126
-rw-r--r--src/statement.rs26
-rw-r--r--src/trace.rs29
-rw-r--r--src/transaction.rs10
-rw-r--r--src/types/chrono.rs58
-rw-r--r--src/types/from_sql.rs83
-rw-r--r--src/types/mod.rs14
-rw-r--r--src/types/serde_json.rs8
-rw-r--r--src/types/time.rs120
-rw-r--r--src/types/to_sql.rs24
-rw-r--r--src/types/value.rs8
-rw-r--r--src/types/value_ref.rs81
-rw-r--r--src/unlock_notify.rs31
-rw-r--r--src/util/small_cstr.rs6
-rw-r--r--src/util/sqlite_string.rs18
-rw-r--r--src/version.rs2
-rw-r--r--src/vtab/array.rs25
-rw-r--r--src/vtab/csvtab.rs6
-rw-r--r--src/vtab/mod.rs163
-rw-r--r--src/vtab/series.rs18
40 files changed, 1666 insertions, 854 deletions
diff --git a/src/backup.rs b/src/backup.rs
index 72d54e5..6da01fd 100644
--- a/src/backup.rs
+++ b/src/backup.rs
@@ -1,4 +1,4 @@
-//! `feature = "backup"` Online SQLite backup API.
+//! Online SQLite backup API.
//!
//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
//! for the source (which can be used while the backup is running) and one for
@@ -40,11 +40,11 @@ use std::time::Duration;
use crate::ffi;
-use crate::error::{error_from_handle, error_from_sqlite_code};
+use crate::error::error_from_handle;
use crate::{Connection, DatabaseName, Result};
impl Connection {
- /// `feature = "backup"` Back up the `name` database to the given
+ /// Back up the `name` database to the given
/// destination path.
///
/// If `progress` is not `None`, it will be called periodically
@@ -84,7 +84,7 @@ impl Connection {
}
}
- /// `feature = "backup"` Restore the given source path into the
+ /// Restore the given source path into the
/// `name` database. If `progress` is not `None`, it will be
/// called periodically until the restore completes.
///
@@ -107,7 +107,7 @@ impl Connection {
let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
let mut r = More;
- let mut busy_count = 0i32;
+ let mut busy_count = 0_i32;
'restore_loop: while r == More || r == Busy {
r = restore.step(100)?;
if let Some(ref f) = progress {
@@ -131,7 +131,7 @@ impl Connection {
}
}
-/// `feature = "backup"` Possible successful results of calling
+/// Possible successful results of calling
/// [`Backup::step`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
@@ -152,7 +152,7 @@ pub enum StepResult {
Locked,
}
-/// `feature = "backup"` Struct specifying the progress of a backup. The
+/// Struct specifying the progress of a backup. The
/// percentage completion can be calculated as `(pagecount - remaining) /
/// pagecount`. The progress of a backup is as of the last call to
/// [`step`](Backup::step) - if the source database is modified after a call to
@@ -166,10 +166,10 @@ pub struct Progress {
pub pagecount: c_int,
}
-/// `feature = "backup"` A handle to an online backup.
+/// A handle to an online backup.
pub struct Backup<'a, 'b> {
phantom_from: PhantomData<&'a Connection>,
- phantom_to: PhantomData<&'b Connection>,
+ to: &'b Connection,
b: *mut ffi::sqlite3_backup,
}
@@ -203,8 +203,8 @@ impl Backup<'_, '_> {
to: &'b mut Connection,
to_name: DatabaseName<'_>,
) -> Result<Backup<'a, 'b>> {
- let to_name = to_name.to_cstring()?;
- let from_name = from_name.to_cstring()?;
+ let to_name = to_name.as_cstring()?;
+ let from_name = from_name.as_cstring()?;
let to_db = to.db.borrow_mut().db;
@@ -223,7 +223,7 @@ impl Backup<'_, '_> {
Ok(Backup {
phantom_from: PhantomData,
- phantom_to: PhantomData,
+ to,
b,
})
}
@@ -231,6 +231,7 @@ impl Backup<'_, '_> {
/// Gets the progress of the backup as of the last call to
/// [`step`](Backup::step).
#[inline]
+ #[must_use]
pub fn progress(&self) -> Progress {
unsafe {
Progress {
@@ -263,7 +264,7 @@ impl Backup<'_, '_> {
ffi::SQLITE_OK => Ok(More),
ffi::SQLITE_BUSY => Ok(Busy),
ffi::SQLITE_LOCKED => Ok(Locked),
- _ => Err(error_from_sqlite_code(rc, None)),
+ _ => self.to.decode_result(rc).map(|_| More),
}
}
@@ -296,7 +297,7 @@ impl Backup<'_, '_> {
loop {
let r = self.step(pages_per_step)?;
if let Some(progress) = progress {
- progress(self.progress())
+ progress(self.progress());
}
match r {
More | Busy | Locked => thread::sleep(pause_between_pages),
diff --git a/src/blob/mod.rs b/src/blob/mod.rs
index 202f65d..81c6098 100644
--- a/src/blob/mod.rs
+++ b/src/blob/mod.rs
@@ -1,4 +1,4 @@
-//! `feature = "blob"` Incremental BLOB I/O.
+//! Incremental BLOB I/O.
//!
//! Note that SQLite does not provide API-level access to change the size of a
//! BLOB; that must be performed through SQL statements.
@@ -196,7 +196,7 @@ use crate::{Connection, DatabaseName, Result};
mod pos_io;
-/// `feature = "blob"` Handle to an open BLOB. See
+/// Handle to an open BLOB. See
/// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
pub struct Blob<'conn> {
conn: &'conn Connection,
@@ -206,7 +206,7 @@ pub struct Blob<'conn> {
}
impl Connection {
- /// `feature = "blob"` Open a handle to the BLOB located in `row_id`,
+ /// Open a handle to the BLOB located in `row_id`,
/// `column`, `table` in database `db`.
///
/// # Failure
@@ -223,9 +223,9 @@ impl Connection {
row_id: i64,
read_only: bool,
) -> Result<Blob<'a>> {
- let mut c = self.db.borrow_mut();
+ let c = self.db.borrow_mut();
let mut blob = ptr::null_mut();
- let db = db.to_cstring()?;
+ let db = db.as_cstring()?;
let table = super::str_to_cstring(table)?;
let column = super::str_to_cstring(column)?;
let rc = unsafe {
@@ -265,12 +265,14 @@ impl Blob<'_> {
/// Return the size in bytes of the BLOB.
#[inline]
+ #[must_use]
pub fn size(&self) -> i32 {
unsafe { ffi::sqlite3_blob_bytes(self.blob) }
}
/// Return the current size in bytes of the BLOB.
#[inline]
+ #[must_use]
pub fn len(&self) -> usize {
use std::convert::TryInto;
self.size().try_into().unwrap()
@@ -278,6 +280,7 @@ impl Blob<'_> {
/// Return true if the BLOB is empty.
#[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.size() == 0
}
@@ -318,8 +321,7 @@ impl io::Read for Blob<'_> {
if n <= 0 {
return Ok(0);
}
- let rc =
- unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr() as *mut _, n, self.pos) };
+ let rc = unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_mut_ptr().cast(), n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
@@ -400,7 +402,7 @@ impl Drop for Blob<'_> {
}
}
-/// `feature = "blob"` BLOB of length N that is filled with zeroes.
+/// BLOB of length N that is filled with zeroes.
///
/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is
/// later written using incremental BLOB I/O routines.
diff --git a/src/blob/pos_io.rs b/src/blob/pos_io.rs
index dd6167d..ecc7d65 100644
--- a/src/blob/pos_io.rs
+++ b/src/blob/pos_io.rs
@@ -44,15 +44,14 @@ impl<'conn> Blob<'conn> {
// losslessly converted to i32, since `len` came from an i32.
// Sanity check the above.
debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
- unsafe {
- check!(ffi::sqlite3_blob_write(
+ self.conn.decode_result(unsafe {
+ ffi::sqlite3_blob_write(
self.blob,
- buf.as_ptr() as *const _,
+ buf.as_ptr().cast(),
buf.len() as i32,
write_start as i32,
- ));
- }
- Ok(())
+ )
+ })
}
/// An alias for `write_at` provided for compatibility with the conceptually
@@ -85,7 +84,7 @@ impl<'conn> Blob<'conn> {
// Safety: this is safe because `raw_read_at` never stores uninitialized
// data into `as_uninit`.
let as_uninit: &mut [MaybeUninit<u8>] =
- unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
+ unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
self.raw_read_at(as_uninit, read_start).map(|s| s.len())
}
@@ -120,7 +119,7 @@ impl<'conn> Blob<'conn> {
// We could return `Ok(&mut [])`, but it seems confusing that the
// pointers don't match, so fabricate a empty slice of u8 with the
// same base pointer as `buf`.
- let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
+ let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), 0) };
return Ok(empty);
}
@@ -151,14 +150,14 @@ impl<'conn> Blob<'conn> {
debug_assert!(i32::try_from(read_len).is_ok());
unsafe {
- check!(ffi::sqlite3_blob_read(
+ self.conn.decode_result(ffi::sqlite3_blob_read(
self.blob,
- buf.as_mut_ptr() as *mut _,
+ buf.as_mut_ptr().cast(),
read_len as i32,
read_start as i32,
- ));
+ ))?;
- Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
+ Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), read_len))
}
}
diff --git a/src/busy.rs b/src/busy.rs
index 447610e..b394d01 100644
--- a/src/busy.rs
+++ b/src/busy.rs
@@ -64,7 +64,7 @@ impl Connection {
0
}
}
- let mut c = self.db.borrow_mut();
+ let c = self.db.borrow_mut();
let r = match callback {
Some(f) => unsafe {
ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void)
@@ -169,7 +169,7 @@ mod test {
let _ = db2
.query_row("PRAGMA schema_version", [], |row| row.get::<_, i32>(0))
.expect("unexpected error");
- assert_eq!(CALLED.load(Ordering::Relaxed), true);
+ assert!(CALLED.load(Ordering::Relaxed));
child.join().unwrap();
}
diff --git a/src/cache.rs b/src/cache.rs
index 89459ce..c80a708 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -46,13 +46,13 @@ impl Connection {
/// can set the capacity manually using this method.
#[inline]
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
- self.cache.set_capacity(capacity)
+ self.cache.set_capacity(capacity);
}
/// Remove/finalize all prepared statements currently in the cache.
#[inline]
pub fn flush_prepared_statement_cache(&self) {
- self.cache.flush()
+ self.cache.flush();
}
}
@@ -60,6 +60,9 @@ impl Connection {
// #[derive(Debug)] // FIXME: https://github.com/kyren/hashlink/pull/4
pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for StatementCache {}
+
/// Cacheable statement.
///
/// Statement will return automatically to the cache by default.
@@ -122,7 +125,7 @@ impl StatementCache {
#[inline]
fn set_capacity(&self, capacity: usize) {
- self.0.borrow_mut().set_capacity(capacity)
+ self.0.borrow_mut().set_capacity(capacity);
}
// Search the cache for a prepared-statement object that implements `sql`.
@@ -169,7 +172,7 @@ impl StatementCache {
#[inline]
fn flush(&self) {
let mut cache = self.0.borrow_mut();
- cache.clear()
+ cache.clear();
}
}
diff --git a/src/collation.rs b/src/collation.rs
index 2b93a9a..c1fe3f7 100644
--- a/src/collation.rs
+++ b/src/collation.rs
@@ -1,4 +1,4 @@
-//! `feature = "collation"` Add, remove, or modify a collation
+//! Add, remove, or modify a collation
use std::cmp::Ordering;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, UnwindSafe};
@@ -10,22 +10,22 @@ use crate::{str_to_cstring, Connection, InnerConnection, Result};
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut T));
+ drop(Box::from_raw(p.cast::<T>()));
}
impl Connection {
- /// `feature = "collation"` Add or modify a collation.
+ /// Add or modify a collation.
#[inline]
- pub fn create_collation<'c, C>(&'c self, collation_name: &str, x_compare: C) -> Result<()>
+ pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
self.db
.borrow_mut()
.create_collation(collation_name, x_compare)
}
- /// `feature = "collation"` Collation needed callback
+ /// Collation needed callback
#[inline]
pub fn collation_needed(
&self,
@@ -34,7 +34,7 @@ impl Connection {
self.db.borrow_mut().collation_needed(x_coll_needed)
}
- /// `feature = "collation"` Remove collation.
+ /// Remove collation.
#[inline]
pub fn remove_collation(&self, collation_name: &str) -> Result<()> {
self.db.borrow_mut().remove_collation(collation_name)
@@ -42,9 +42,9 @@ impl Connection {
}
impl InnerConnection {
- fn create_collation<'c, C>(&'c mut self, collation_name: &str, x_compare: C) -> Result<()>
+ fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()>
where
- C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
+ C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static,
{
unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void,
@@ -57,14 +57,14 @@ impl InnerConnection {
C: Fn(&str, &str) -> Ordering,
{
let r = catch_unwind(|| {
- let boxed_f: *mut C = arg1 as *mut C;
+ let boxed_f: *mut C = arg1.cast::<C>();
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let s1 = {
- let c_slice = slice::from_raw_parts(arg3 as *const u8, arg2 as usize);
+ let c_slice = slice::from_raw_parts(arg3.cast::<u8>(), arg2 as usize);
String::from_utf8_lossy(c_slice)
};
let s2 = {
- let c_slice = slice::from_raw_parts(arg5 as *const u8, arg4 as usize);
+ let c_slice = slice::from_raw_parts(arg5.cast::<u8>(), arg4 as usize);
String::from_utf8_lossy(c_slice)
};
(*boxed_f)(s1.as_ref(), s2.as_ref())
@@ -91,7 +91,7 @@ impl InnerConnection {
self.db(),
c_name.as_ptr(),
flags,
- boxed_f as *mut c_void,
+ boxed_f.cast::<c_void>(),
Some(call_boxed_closure::<C>),
Some(free_boxed_value::<C>),
)
diff --git a/src/column.rs b/src/column.rs
index b9122c4..aa1f5f7 100644
--- a/src/column.rs
+++ b/src/column.rs
@@ -1,6 +1,6 @@
use std::str;
-use crate::{Error, Result, Row, Rows, Statement};
+use crate::{Error, Result, Statement};
/// Information about a column of a SQLite query.
#[derive(Debug)]
@@ -12,12 +12,14 @@ pub struct Column<'stmt> {
impl Column<'_> {
/// Returns the name of the column.
#[inline]
+ #[must_use]
pub fn name(&self) -> &str {
self.name
}
/// Returns the type of the column (`None` for expression).
#[inline]
+ #[must_use]
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
@@ -132,6 +134,7 @@ impl Statement<'_> {
/// sure that current statement has already been stepped once before
/// calling this method.
#[cfg(feature = "column_decltype")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
pub fn columns(&self) -> Vec<Column> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize);
@@ -147,72 +150,6 @@ impl Statement<'_> {
}
}
-impl<'stmt> Rows<'stmt> {
- /// Get all the column names.
- #[inline]
- pub fn column_names(&self) -> Option<Vec<&str>> {
- self.stmt.map(Statement::column_names)
- }
-
- /// Return the number of columns.
- #[inline]
- pub fn column_count(&self) -> Option<usize> {
- self.stmt.map(Statement::column_count)
- }
-
- /// Return the name of the column.
- #[inline]
- pub fn column_name(&self, col: usize) -> Option<Result<&str>> {
- self.stmt.map(|stmt| stmt.column_name(col))
- }
-
- /// Return the index of the column.
- #[inline]
- pub fn column_index(&self, name: &str) -> Option<Result<usize>> {
- self.stmt.map(|stmt| stmt.column_index(name))
- }
-
- /// Returns a slice describing the columns of the Rows.
- #[inline]
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Option<Vec<Column>> {
- self.stmt.map(Statement::columns)
- }
-}
-
-impl<'stmt> Row<'stmt> {
- /// Get all the column names of the Row.
- #[inline]
- pub fn column_names(&self) -> Vec<&str> {
- self.stmt.column_names()
- }
-
- /// Return the number of columns in the current row.
- #[inline]
- pub fn column_count(&self) -> usize {
- self.stmt.column_count()
- }
-
- /// Return the name of the column.
- #[inline]
- pub fn column_name(&self, col: usize) -> Result<&str> {
- self.stmt.column_name(col)
- }
-
- /// Return the index of the column.
- #[inline]
- pub fn column_index(&self, name: &str) -> Result<usize> {
- self.stmt.column_index(name)
- }
-
- /// Returns a slice describing the columns of the Row.
- #[inline]
- #[cfg(feature = "column_decltype")]
- pub fn columns(&self) -> Vec<Column> {
- self.stmt.columns()
- }
-}
-
#[cfg(test)]
mod test {
use crate::{Connection, Result};
@@ -230,10 +167,17 @@ mod test {
column_names.as_slice(),
&["type", "name", "tbl_name", "rootpage", "sql"]
);
- let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
+ let column_types: Vec<Option<String>> = columns
+ .iter()
+ .map(|col| col.decl_type().map(str::to_lowercase))
+ .collect();
assert_eq!(
&column_types[..3],
- &[Some("text"), Some("text"), Some("text"),]
+ &[
+ Some("text".to_owned()),
+ Some("text".to_owned()),
+ Some("text".to_owned()),
+ ]
);
Ok(())
}
diff --git a/src/config.rs b/src/config.rs
index ff4762e..b59e5ef 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -2,6 +2,7 @@
use std::os::raw::c_int;
+use crate::error::check;
use crate::ffi;
use crate::{Connection, Result};
@@ -30,7 +31,9 @@ pub enum DbConfig {
/// Includes or excludes output for any operations performed by trigger
/// programs from the output of EXPLAIN QUERY PLAN commands.
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
- //SQLITE_DBCONFIG_RESET_DATABASE = 1009,
+ /// 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,
/// Activates or deactivates the "defensive" flag for a database connection.
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
/// Activates or deactivates the "writable_schema" flag.
@@ -63,58 +66,58 @@ pub enum DbConfig {
impl Connection {
/// Returns the current value of a `config`.
///
- /// - SQLITE_DBCONFIG_ENABLE_FKEY: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: return `false` or `true` to indicate
/// whether FK enforcement is off or on
- /// - SQLITE_DBCONFIG_ENABLE_TRIGGER: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: return `false` or `true` to indicate
/// whether triggers are disabled or enabled
- /// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return `false` or `true` to
- /// indicate whether fts3_tokenizer are disabled or enabled
- /// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return `false` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: return `false` or `true` to
+ /// indicate whether `fts3_tokenizer` are disabled or enabled
+ /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: return `false` to indicate
/// checkpoints-on-close are not disabled or `true` if they are
- /// - SQLITE_DBCONFIG_ENABLE_QPSG: return `false` or `true` to indicate
+ /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: return `false` or `true` to indicate
/// whether the QPSG is disabled or enabled
- /// - SQLITE_DBCONFIG_TRIGGER_EQP: return `false` to indicate
+ /// - `SQLITE_DBCONFIG_TRIGGER_EQP`: return `false` to indicate
/// output-for-trigger are not disabled or `true` if it is
#[inline]
pub fn db_config(&self, config: DbConfig) -> Result<bool> {
let c = self.db.borrow();
unsafe {
let mut val = 0;
- check!(ffi::sqlite3_db_config(
+ check(ffi::sqlite3_db_config(
c.db(),
config as c_int,
-1,
- &mut val
- ));
+ &mut val,
+ ))?;
Ok(val != 0)
}
}
/// Make configuration changes to a database connection
///
- /// - SQLITE_DBCONFIG_ENABLE_FKEY: `false` to disable FK enforcement, `true`
- /// to enable FK enforcement
- /// - SQLITE_DBCONFIG_ENABLE_TRIGGER: `false` to disable triggers, `true` to
- /// enable triggers
- /// - SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: `false` to disable
- /// fts3_tokenizer(), `true` to enable fts3_tokenizer()
- /// - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: `false` (the default) to enable
+ /// - `SQLITE_DBCONFIG_ENABLE_FKEY`: `false` to disable FK enforcement,
+ /// `true` to enable FK enforcement
+ /// - `SQLITE_DBCONFIG_ENABLE_TRIGGER`: `false` to disable triggers, `true`
+ /// to enable triggers
+ /// - `SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER`: `false` to disable
+ /// `fts3_tokenizer()`, `true` to enable `fts3_tokenizer()`
+ /// - `SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`: `false` (the default) to enable
/// checkpoints-on-close, `true` to disable them
- /// - SQLITE_DBCONFIG_ENABLE_QPSG: `false` to disable the QPSG, `true` to
+ /// - `SQLITE_DBCONFIG_ENABLE_QPSG`: `false` to disable the QPSG, `true` to
/// enable QPSG
- /// - SQLITE_DBCONFIG_TRIGGER_EQP: `false` to disable output for trigger
+ /// - `SQLITE_DBCONFIG_TRIGGER_EQP`: `false` to disable output for trigger
/// programs, `true` to enable it
#[inline]
pub fn set_db_config(&self, config: DbConfig, new_val: bool) -> Result<bool> {
let c = self.db.borrow_mut();
unsafe {
let mut val = 0;
- check!(ffi::sqlite3_db_config(
+ check(ffi::sqlite3_db_config(
c.db(),
config as c_int,
if new_val { 1 } else { 0 },
- &mut val
- ));
+ &mut val,
+ ))?;
Ok(val != 0)
}
}
diff --git a/src/context.rs b/src/context.rs
index ce60ebb..5f935fa 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -58,11 +58,11 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 {
- ffi::sqlite3_result_zeroblob(ctx, 0)
+ ffi::sqlite3_result_zeroblob(ctx, 0);
} else {
ffi::sqlite3_result_blob(
ctx,
- b.as_ptr() as *const c_void,
+ b.as_ptr().cast::<c_void>(),
length as c_int,
ffi::SQLITE_TRANSIENT(),
);
diff --git a/src/error.rs b/src/error.rs
index a8d3f23..129f697 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,6 +1,6 @@
use crate::types::FromSqlError;
use crate::types::Type;
-use crate::{errmsg_to_string, ffi};
+use crate::{errmsg_to_string, ffi, Result};
use std::error;
use std::fmt;
use std::os::raw::c_int;
@@ -72,15 +72,18 @@ pub enum Error {
/// [`functions::Context::get`](crate::functions::Context::get) when the
/// function argument cannot be converted to the requested type.
#[cfg(feature = "functions")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
InvalidFunctionParameterType(usize, Type),
/// Error returned by [`vtab::Values::get`](crate::vtab::Values::get) when
/// the filter argument cannot be converted to the requested type.
#[cfg(feature = "vtab")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
InvalidFilterParameterType(usize, Type),
/// An error case available for implementors of custom user functions (e.g.,
/// [`create_scalar_function`](crate::Connection::create_scalar_function)).
#[cfg(feature = "functions")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
#[allow(dead_code)]
UserFunctionError(Box<dyn error::Error + Send + Sync + 'static>),
@@ -94,11 +97,13 @@ pub enum Error {
/// An error case available for implementors of custom modules (e.g.,
/// [`create_module`](crate::Connection::create_module)).
#[cfg(feature = "vtab")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
#[allow(dead_code)]
ModuleError(String),
/// An unwinding panic occurs in an UDF (user-defined function).
#[cfg(feature = "functions")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
UnwindingPanic,
/// An error returned when
@@ -106,6 +111,7 @@ pub enum Error {
/// retrieve data of a different type than what had been stored using
/// [`Context::set_aux`](crate::functions::Context::set_aux).
#[cfg(feature = "functions")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
GetAuxWrongType,
/// Error when the SQL contains multiple statements.
@@ -120,6 +126,7 @@ pub enum Error {
/// [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) will
/// return it if the blob has insufficient data.
#[cfg(feature = "blob")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
BlobSizeError,
}
@@ -195,12 +202,7 @@ impl From<FromSqlError> for Error {
// context.
match err {
FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
}
FromSqlError::Other(source) => {
@@ -350,11 +352,10 @@ pub unsafe fn error_from_handle(db: *mut ffi::sqlite3, code: c_int) -> Error {
error_from_sqlite_code(code, message)
}
-macro_rules! check {
- ($funcall:expr) => {{
- let rc = $funcall;
- if rc != crate::ffi::SQLITE_OK {
- return Err(crate::error::error_from_sqlite_code(rc, None).into());
- }
- }};
+pub fn check(code: c_int) -> Result<()> {
+ if code != crate::ffi::SQLITE_OK {
+ Err(crate::error::error_from_sqlite_code(code, None))
+ } else {
+ Ok(())
+ }
}
diff --git a/src/functions.rs b/src/functions.rs
index ce908d5..e613182 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -1,4 +1,4 @@
-//! `feature = "functions"` Create or redefine SQL functions.
+//! Create or redefine SQL functions.
//!
//! # Example
//!
@@ -84,27 +84,24 @@ unsafe fn report_error(ctx: *mut sqlite3_context, err: &Error) {
ffi::SQLITE_CONSTRAINT
}
- match *err {
- Error::SqliteFailure(ref err, ref s) => {
- ffi::sqlite3_result_error_code(ctx, err.extended_code);
- if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
- ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
- }
+ if let Error::SqliteFailure(ref err, ref s) = *err {
+ ffi::sqlite3_result_error_code(ctx, err.extended_code);
+ if let Some(Ok(cstr)) = s.as_ref().map(|s| str_to_cstring(s)) {
+ ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
- _ => {
- ffi::sqlite3_result_error_code(ctx, constraint_error_code());
- if let Ok(cstr) = str_to_cstring(&err.to_string()) {
- ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
- }
+ } else {
+ ffi::sqlite3_result_error_code(ctx, constraint_error_code());
+ if let Ok(cstr) = str_to_cstring(&err.to_string()) {
+ ffi::sqlite3_result_error(ctx, cstr.as_ptr(), -1);
}
}
}
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut T));
+ drop(Box::from_raw(p.cast::<T>()));
}
-/// `feature = "functions"` Context is a wrapper for the SQLite function
+/// Context is a wrapper for the SQLite function
/// evaluation context.
pub struct Context<'a> {
ctx: *mut sqlite3_context,
@@ -114,12 +111,14 @@ pub struct Context<'a> {
impl Context<'_> {
/// Returns the number of arguments to the function.
#[inline]
+ #[must_use]
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` when there is no argument.
#[inline]
+ #[must_use]
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
@@ -144,12 +143,7 @@ impl Context<'_> {
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ FromSqlError::InvalidBlobSize { .. } => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
})
@@ -162,6 +156,7 @@ impl Context<'_> {
/// Will panic if `idx` is greater than or equal to
/// [`self.len()`](Context::len).
#[inline]
+ #[must_use]
pub fn get_raw(&self, idx: usize) -> ValueRef<'_> {
let arg = self.args[idx];
unsafe { ValueRef::from_value(arg) }
@@ -203,9 +198,9 @@ impl Context<'_> {
ffi::sqlite3_set_auxdata(
self.ctx,
arg,
- raw as *mut _,
+ raw.cast(),
Some(free_boxed_value::<AuxInner>),
- )
+ );
};
Ok(orig)
}
@@ -260,7 +255,7 @@ impl Deref for ConnectionRef<'_> {
type AuxInner = Arc<dyn Any + Send + Sync + 'static>;
-/// `feature = "functions"` Aggregate is the callback interface for user-defined
+/// Aggregate is the callback interface for user-defined
/// aggregate function.
///
/// `A` is the type of the aggregation context and `T` is the type of the final
@@ -292,9 +287,10 @@ where
fn finalize(&self, _: &mut Context<'_>, _: Option<A>) -> Result<T>;
}
-/// `feature = "window"` WindowAggregate is the callback interface for
+/// `WindowAggregate` is the callback interface for
/// user-defined aggregate window function.
#[cfg(feature = "window")]
+#[cfg_attr(docsrs, doc(cfg(feature = "window")))]
pub trait WindowAggregate<A, T>: Aggregate<A, T>
where
A: RefUnwindSafe + UnwindSafe,
@@ -341,7 +337,7 @@ impl Default for FunctionFlags {
}
impl Connection {
- /// `feature = "functions"` Attach a user-defined scalar function to
+ /// Attach a user-defined scalar function to
/// this database connection.
///
/// `fn_name` is the name the function will be accessible from SQL.
@@ -379,15 +375,15 @@ impl Connection {
///
/// Will return Err if the function could not be attached to the connection.
#[inline]
- pub fn create_scalar_function<'c, F, T>(
- &'c self,
+ pub fn create_scalar_function<F, T>(
+ &self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql,
{
self.db
@@ -395,7 +391,7 @@ impl Connection {
.create_scalar_function(fn_name, n_arg, flags, x_func)
}
- /// `feature = "functions"` Attach a user-defined aggregate function to this
+ /// Attach a user-defined aggregate function to this
/// database connection.
///
/// # Failure
@@ -411,7 +407,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- D: Aggregate<A, T>,
+ D: Aggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -419,12 +415,13 @@ impl Connection {
.create_aggregate_function(fn_name, n_arg, flags, aggr)
}
- /// `feature = "window"` Attach a user-defined aggregate window function to
+ /// Attach a user-defined aggregate window function to
/// this database connection.
///
/// See `https://sqlite.org/windowfunctions.html#udfwinfunc` for more
/// information.
#[cfg(feature = "window")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "window")))]
#[inline]
pub fn create_window_function<A, W, T>(
&self,
@@ -435,7 +432,7 @@ impl Connection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- W: WindowAggregate<A, T>,
+ W: WindowAggregate<A, T> + 'static,
T: ToSql,
{
self.db
@@ -443,7 +440,7 @@ impl Connection {
.create_window_function(fn_name, n_arg, flags, aggr)
}
- /// `feature = "functions"` Removes a user-defined function from this
+ /// Removes a user-defined function from this
/// database connection.
///
/// `fn_name` and `n_arg` should match the name and number of arguments
@@ -460,15 +457,15 @@ impl Connection {
}
impl InnerConnection {
- fn create_scalar_function<'c, F, T>(
- &'c mut self,
+ fn create_scalar_function<F, T>(
+ &mut self,
fn_name: &str,
n_arg: c_int,
flags: FunctionFlags,
x_func: F,
) -> Result<()>
where
- F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
+ F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'static,
T: ToSql,
{
unsafe extern "C" fn call_boxed_closure<F, T>(
@@ -480,7 +477,7 @@ impl InnerConnection {
T: ToSql,
{
let r = catch_unwind(|| {
- let boxed_f: *mut F = ffi::sqlite3_user_data(ctx) as *mut F;
+ let boxed_f: *mut F = ffi::sqlite3_user_data(ctx).cast::<F>();
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
let ctx = Context {
ctx,
@@ -512,7 +509,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_f as *mut c_void,
+ boxed_f.cast::<c_void>(),
Some(call_boxed_closure::<F, T>),
None,
None,
@@ -531,7 +528,7 @@ impl InnerConnection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- D: Aggregate<A, T>,
+ D: Aggregate<A, T> + 'static,
T: ToSql,
{
let boxed_aggr: *mut D = Box::into_raw(Box::new(aggr));
@@ -542,7 +539,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_aggr as *mut c_void,
+ boxed_aggr.cast::<c_void>(),
None,
Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>),
@@ -562,7 +559,7 @@ impl InnerConnection {
) -> Result<()>
where
A: RefUnwindSafe + UnwindSafe,
- W: WindowAggregate<A, T>,
+ W: WindowAggregate<A, T> + 'static,
T: ToSql,
{
let boxed_aggr: *mut W = Box::into_raw(Box::new(aggr));
@@ -573,7 +570,7 @@ impl InnerConnection {
c_name.as_ptr(),
n_arg,
flags.bits(),
- boxed_aggr as *mut c_void,
+ boxed_aggr.cast::<c_void>(),
Some(call_boxed_step::<A, W, T>),
Some(call_boxed_final::<A, W, T>),
Some(call_boxed_value::<A, W, T>),
@@ -620,16 +617,15 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
D: Aggregate<A, T>,
T: ToSql,
{
- let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
- Some(pac) => pac,
- None => {
- ffi::sqlite3_result_error_nomem(ctx);
- return;
- }
+ let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ pac
+ } else {
+ ffi::sqlite3_result_error_nomem(ctx);
+ return;
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
+ let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -668,16 +664,15 @@ unsafe extern "C" fn call_boxed_inverse<A, W, T>(
W: WindowAggregate<A, T>,
T: ToSql,
{
- let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
- Some(pac) => pac,
- None => {
- ffi::sqlite3_result_error_nomem(ctx);
- return;
- }
+ let pac = if let Some(pac) = aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) {
+ pac
+ } else {
+ ffi::sqlite3_result_error_nomem(ctx);
+ return;
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
+ let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -722,7 +717,7 @@ where
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx) as *mut D;
+ let boxed_aggr: *mut D = ffi::sqlite3_user_data(ctx).cast::<D>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -767,7 +762,7 @@ where
};
let r = catch_unwind(|| {
- let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx) as *mut W;
+ let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
assert!(
!boxed_aggr.is_null(),
"Internal error - null aggregate pointer"
@@ -883,7 +878,7 @@ mod test {
let result: Result<bool> =
db.query_row("SELECT regexp('l.s[aeiouy]', 'lisa')", [], |r| r.get(0));
- assert_eq!(true, result?);
+ assert!(result?);
let result: Result<i64> = db.query_row(
"SELECT COUNT(*) FROM foo WHERE regexp('l.s[aeiouy]', x) == 1",
diff --git a/src/hooks.rs b/src/hooks.rs
index 71e2f4c..f0ae1f3 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -1,4 +1,4 @@
-//! `feature = "hooks"` Commit, Data Change and Rollback Notification Callbacks
+//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void};
@@ -9,7 +9,7 @@ use crate::ffi;
use crate::{Connection, InnerConnection};
-/// `feature = "hooks"` Action Codes
+/// Action Codes
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
@@ -37,50 +37,350 @@ impl From<i32> for Action {
}
}
+/// The context recieved by an authorizer hook.
+///
+/// See <https://sqlite.org/c3ref/set_authorizer.html> for more info.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct AuthContext<'c> {
+ /// The action to be authorized.
+ pub action: AuthAction<'c>,
+
+ /// The database name, if applicable.
+ pub database_name: Option<&'c str>,
+
+ /// The inner-most trigger or view responsible for the access attempt.
+ /// `None` if the access attempt was made by top-level SQL code.
+ pub accessor: Option<&'c str>,
+}
+
+/// Actions and arguments found within a statement during
+/// preparation.
+///
+/// See <https://sqlite.org/c3ref/c_alter_table.html> for more info.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+#[allow(missing_docs)]
+pub enum AuthAction<'c> {
+ /// This variant is not normally produced by SQLite. You may encounter it
+ // if you're using a different version than what's supported by this library.
+ Unknown {
+ /// The unknown authorization action code.
+ code: i32,
+ /// The third arg to the authorizer callback.
+ arg1: Option<&'c str>,
+ /// The fourth arg to the authorizer callback.
+ arg2: Option<&'c str>,
+ },
+ CreateIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTable {
+ table_name: &'c str,
+ },
+ CreateTempIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTempTable {
+ table_name: &'c str,
+ },
+ CreateTempTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateTempView {
+ view_name: &'c str,
+ },
+ CreateTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ CreateView {
+ view_name: &'c str,
+ },
+ Delete {
+ table_name: &'c str,
+ },
+ DropIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTable {
+ table_name: &'c str,
+ },
+ DropTempIndex {
+ index_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTempTable {
+ table_name: &'c str,
+ },
+ DropTempTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ DropTempView {
+ view_name: &'c str,
+ },
+ DropTrigger {
+ trigger_name: &'c str,
+ table_name: &'c str,
+ },
+ DropView {
+ view_name: &'c str,
+ },
+ Insert {
+ table_name: &'c str,
+ },
+ Pragma {
+ pragma_name: &'c str,
+ /// The pragma value, if present (e.g., `PRAGMA name = value;`).
+ pragma_value: Option<&'c str>,
+ },
+ Read {
+ table_name: &'c str,
+ column_name: &'c str,
+ },
+ Select,
+ Transaction {
+ operation: TransactionOperation,
+ },
+ Update {
+ table_name: &'c str,
+ column_name: &'c str,
+ },
+ Attach {
+ filename: &'c str,
+ },
+ Detach {
+ database_name: &'c str,
+ },
+ AlterTable {
+ database_name: &'c str,
+ table_name: &'c str,
+ },
+ Reindex {
+ index_name: &'c str,
+ },
+ Analyze {
+ table_name: &'c str,
+ },
+ CreateVtable {
+ table_name: &'c str,
+ module_name: &'c str,
+ },
+ DropVtable {
+ table_name: &'c str,
+ module_name: &'c str,
+ },
+ Function {
+ function_name: &'c str,
+ },
+ Savepoint {
+ operation: TransactionOperation,
+ savepoint_name: &'c str,
+ },
+ #[cfg(feature = "modern_sqlite")]
+ Recursive,
+}
+
+impl<'c> AuthAction<'c> {
+ fn from_raw(code: i32, arg1: Option<&'c str>, arg2: Option<&'c str>) -> Self {
+ match (code, arg1, arg2) {
+ (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex {
+ index_name,
+ table_name,
+ },
+ (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name },
+ (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => {
+ Self::CreateTempIndex {
+ index_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => {
+ Self::CreateTempTable { table_name }
+ }
+ (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::CreateTempTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => {
+ Self::CreateTempView { view_name }
+ }
+ (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::CreateTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name },
+ (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name },
+ (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex {
+ index_name,
+ table_name,
+ },
+ (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name },
+ (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => {
+ Self::DropTempIndex {
+ index_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => {
+ Self::DropTempTable { table_name }
+ }
+ (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
+ Self::DropTempTrigger {
+ trigger_name,
+ table_name,
+ }
+ }
+ (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name },
+ (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger {
+ trigger_name,
+ table_name,
+ },
+ (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name },
+ (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name },
+ (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma {
+ pragma_name,
+ pragma_value,
+ },
+ (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read {
+ table_name,
+ column_name,
+ },
+ (ffi::SQLITE_SELECT, ..) => Self::Select,
+ (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction {
+ operation: TransactionOperation::from_str(operation_str),
+ },
+ (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update {
+ table_name,
+ column_name,
+ },
+ (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename },
+ (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name },
+ (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable {
+ database_name,
+ table_name,
+ },
+ (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name },
+ (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name },
+ (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => {
+ Self::CreateVtable {
+ table_name,
+ module_name,
+ }
+ }
+ (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable {
+ table_name,
+ module_name,
+ },
+ (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name },
+ (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint {
+ operation: TransactionOperation::from_str(operation_str),
+ savepoint_name,
+ },
+ #[cfg(feature = "modern_sqlite")]
+ (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
+ (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
+ }
+ }
+}
+
+pub(crate) type BoxedAuthorizer =
+ Box<dyn for<'c> FnMut(AuthContext<'c>) -> Authorization + Send + 'static>;
+
+/// A transaction operation.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+#[allow(missing_docs)]
+pub enum TransactionOperation {
+ Unknown,
+ Begin,
+ Release,
+ Rollback,
+}
+
+impl TransactionOperation {
+ fn from_str(op_str: &str) -> Self {
+ match op_str {
+ "BEGIN" => Self::Begin,
+ "RELEASE" => Self::Release,
+ "ROLLBACK" => Self::Rollback,
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// [`authorizer`](Connection::authorizer) return code
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+pub enum Authorization {
+ /// Authorize the action.
+ Allow,
+ /// Don't allow access, but don't trigger an error either.
+ Ignore,
+ /// Trigger an error.
+ Deny,
+}
+
+impl Authorization {
+ fn into_raw(self) -> c_int {
+ match self {
+ Self::Allow => ffi::SQLITE_OK,
+ Self::Ignore => ffi::SQLITE_IGNORE,
+ Self::Deny => ffi::SQLITE_DENY,
+ }
+ }
+}
+
impl Connection {
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a transaction is committed.
///
/// The callback returns `true` to rollback.
#[inline]
- pub fn commit_hook<'c, F>(&'c self, hook: Option<F>)
+ pub fn commit_hook<F>(&self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'c,
+ F: FnMut() -> bool + Send + 'static,
{
self.db.borrow_mut().commit_hook(hook);
}
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a transaction is committed.
- ///
- /// The callback returns `true` to rollback.
#[inline]
- pub fn rollback_hook<'c, F>(&'c self, hook: Option<F>)
+ pub fn rollback_hook<F>(&self, hook: Option<F>)
where
- F: FnMut() + Send + 'c,
+ F: FnMut() + Send + 'static,
{
self.db.borrow_mut().rollback_hook(hook);
}
- /// `feature = "hooks"` Register a callback function to be invoked whenever
+ /// Register a callback function to be invoked whenever
/// a row is updated, inserted or deleted in a rowid table.
///
/// The callback parameters are:
///
- /// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or
- /// SQLITE_DELETE),
+ /// - the type of database update (`SQLITE_INSERT`, `SQLITE_UPDATE` or
+ /// `SQLITE_DELETE`),
/// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated,
/// - the ROWID of the row that is updated.
#[inline]
- pub fn update_hook<'c, F>(&'c self, hook: Option<F>)
+ pub fn update_hook<F>(&self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'c,
+ F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
self.db.borrow_mut().update_hook(hook);
}
- /// `feature = "hooks"` Register a query progress callback.
+ /// Register a query progress callback.
///
/// The parameter `num_ops` is the approximate number of virtual machine
/// instructions that are evaluated between successive invocations of the
@@ -94,6 +394,16 @@ impl Connection {
{
self.db.borrow_mut().progress_handler(num_ops, handler);
}
+
+ /// Register an authorizer callback that's invoked
+ /// as a statement is being prepared.
+ #[inline]
+ pub fn authorizer<'c, F>(&self, hook: Option<F>)
+ where
+ F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
+ {
+ self.db.borrow_mut().authorizer(hook);
+ }
}
impl InnerConnection {
@@ -103,18 +413,19 @@ impl InnerConnection {
self.commit_hook(None::<fn() -> bool>);
self.rollback_hook(None::<fn()>);
self.progress_handler(0, None::<fn() -> bool>);
+ self.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
}
- fn commit_hook<'c, F>(&'c mut self, hook: Option<F>)
+ fn commit_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut() -> bool + Send + 'c,
+ F: FnMut() -> bool + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where
F: FnMut() -> bool,
{
let r = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)()
});
if let Ok(true) = r {
@@ -140,7 +451,7 @@ impl InnerConnection {
ffi::sqlite3_commit_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -154,18 +465,18 @@ impl InnerConnection {
self.free_commit_hook = free_commit_hook;
}
- fn rollback_hook<'c, F>(&'c mut self, hook: Option<F>)
+ fn rollback_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut() + Send + 'c,
+ F: FnMut() + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where
F: FnMut(),
{
- let _ = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ drop(catch_unwind(|| {
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)();
- });
+ }));
}
let free_rollback_hook = if hook.is_some() {
@@ -181,7 +492,7 @@ impl InnerConnection {
ffi::sqlite3_rollback_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -195,41 +506,29 @@ impl InnerConnection {
self.free_rollback_hook = free_rollback_hook;
}
- fn update_hook<'c, F>(&'c mut self, hook: Option<F>)
+ fn update_hook<F>(&mut self, hook: Option<F>)
where
- F: FnMut(Action, &str, &str, i64) + Send + 'c,
+ F: FnMut(Action, &str, &str, i64) + Send + 'static,
{
unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void,
action_code: c_int,
- db_str: *const c_char,
- tbl_str: *const c_char,
+ p_db_name: *const c_char,
+ p_table_name: *const c_char,
row_id: i64,
) where
F: FnMut(Action, &str, &str, i64),
{
- use std::ffi::CStr;
- use std::str;
-
let action = Action::from(action_code);
- let db_name = {
- let c_slice = CStr::from_ptr(db_str).to_bytes();
- str::from_utf8(c_slice)
- };
- let tbl_name = {
- let c_slice = CStr::from_ptr(tbl_str).to_bytes();
- str::from_utf8(c_slice)
- };
-
- let _ = catch_unwind(|| {
- let boxed_hook: *mut F = p_arg as *mut F;
+ drop(catch_unwind(|| {
+ let boxed_hook: *mut F = p_arg.cast::<F>();
(*boxed_hook)(
action,
- db_name.expect("illegal db name"),
- tbl_name.expect("illegal table name"),
+ expect_utf8(p_db_name, "database name"),
+ expect_utf8(p_table_name, "table name"),
row_id,
);
- });
+ }));
}
let free_update_hook = if hook.is_some() {
@@ -245,7 +544,7 @@ impl InnerConnection {
ffi::sqlite3_update_hook(
self.db(),
Some(call_boxed_closure::<F>),
- boxed_hook as *mut _,
+ boxed_hook.cast(),
)
}
}
@@ -268,7 +567,7 @@ impl InnerConnection {
F: FnMut() -> bool,
{
let r = catch_unwind(|| {
- let boxed_handler: *mut F = p_arg as *mut F;
+ let boxed_handler: *mut F = p_arg.cast::<F>();
(*boxed_handler)()
});
if let Ok(true) = r {
@@ -278,29 +577,108 @@ impl InnerConnection {
}
}
- match handler {
- Some(handler) => {
- let boxed_handler = Box::new(handler);
- unsafe {
- ffi::sqlite3_progress_handler(
- self.db(),
- num_ops,
- Some(call_boxed_closure::<F>),
- &*boxed_handler as *const F as *mut _,
- )
- }
- self.progress_handler = Some(boxed_handler);
- }
- _ => {
- unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
- self.progress_handler = None;
+ if let Some(handler) = handler {
+ let boxed_handler = Box::new(handler);
+ unsafe {
+ ffi::sqlite3_progress_handler(
+ self.db(),
+ num_ops,
+ Some(call_boxed_closure::<F>),
+ &*boxed_handler as *const F as *mut _,
+ );
}
+ self.progress_handler = Some(boxed_handler);
+ } else {
+ unsafe { ffi::sqlite3_progress_handler(self.db(), num_ops, None, ptr::null_mut()) }
+ self.progress_handler = None;
};
}
+
+ fn authorizer<'c, F>(&'c mut self, authorizer: Option<F>)
+ where
+ F: for<'r> FnMut(AuthContext<'r>) -> Authorization + Send + RefUnwindSafe + 'static,
+ {
+ unsafe extern "C" fn call_boxed_closure<'c, F>(
+ p_arg: *mut c_void,
+ action_code: c_int,
+ param1: *const c_char,
+ param2: *const c_char,
+ db_name: *const c_char,
+ trigger_or_view_name: *const c_char,
+ ) -> c_int
+ where
+ F: FnMut(AuthContext<'c>) -> Authorization + Send + 'static,
+ {
+ catch_unwind(|| {
+ let action = AuthAction::from_raw(
+ action_code,
+ expect_optional_utf8(param1, "authorizer param 1"),
+ expect_optional_utf8(param2, "authorizer param 2"),
+ );
+ let auth_ctx = AuthContext {
+ action,
+ database_name: expect_optional_utf8(db_name, "database name"),
+ accessor: expect_optional_utf8(
+ trigger_or_view_name,
+ "accessor (inner-most trigger or view)",
+ ),
+ };
+ let boxed_hook: *mut F = p_arg.cast::<F>();
+ (*boxed_hook)(auth_ctx)
+ })
+ .map_or_else(|_| ffi::SQLITE_ERROR, Authorization::into_raw)
+ }
+
+ let callback_fn = authorizer
+ .as_ref()
+ .map(|_| call_boxed_closure::<'c, F> as unsafe extern "C" fn(_, _, _, _, _, _) -> _);
+ let boxed_authorizer = authorizer.map(Box::new);
+
+ match unsafe {
+ ffi::sqlite3_set_authorizer(
+ self.db(),
+ callback_fn,
+ boxed_authorizer
+ .as_ref()
+ .map_or_else(ptr::null_mut, |f| &**f as *const F as *mut _),
+ )
+ } {
+ ffi::SQLITE_OK => {
+ self.authorizer = boxed_authorizer.map(|ba| ba as _);
+ }
+ err_code => {
+ // The only error that `sqlite3_set_authorizer` returns is `SQLITE_MISUSE`
+ // when compiled with `ENABLE_API_ARMOR` and the db pointer is invalid.
+ // This library does not allow constructing a null db ptr, so if this branch
+ // is hit, something very bad has happened. Panicking instead of returning
+ // `Result` keeps this hook's API consistent with the others.
+ panic!("unexpectedly failed to set_authorizer: {}", unsafe {
+ crate::error::error_from_handle(self.db(), err_code)
+ });
+ }
+ }
+ }
}
unsafe fn free_boxed_hook<F>(p: *mut c_void) {
- drop(Box::from_raw(p as *mut F));
+ drop(Box::from_raw(p.cast::<F>()));
+}
+
+unsafe fn expect_utf8<'a>(p_str: *const c_char, description: &'static str) -> &'a str {
+ expect_optional_utf8(p_str, description)
+ .unwrap_or_else(|| panic!("received empty {}", description))
+}
+
+unsafe fn expect_optional_utf8<'a>(
+ p_str: *const c_char,
+ description: &'static str,
+) -> Option<&'a str> {
+ if p_str.is_null() {
+ return None;
+ }
+ std::str::from_utf8(std::ffi::CStr::from_ptr(p_str).to_bytes())
+ .unwrap_or_else(|_| panic!("received non-utf8 string as {}", description))
+ .into()
}
#[cfg(test)]
@@ -313,13 +691,13 @@ mod test {
fn test_commit_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = false;
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.commit_hook(Some(|| {
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
false
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -341,12 +719,12 @@ mod test {
fn test_rollback_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = false;
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.rollback_hook(Some(|| {
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
}));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -354,17 +732,17 @@ mod test {
fn test_update_hook() -> Result<()> {
let db = Connection::open_in_memory()?;
- let mut called = false;
+ static CALLED: AtomicBool = AtomicBool::new(false);
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db);
assert_eq!("foo", tbl);
assert_eq!(1, row_id);
- called = true;
+ CALLED.store(true, Ordering::Relaxed);
}));
db.execute_batch("CREATE TABLE foo (t TEXT)")?;
db.execute_batch("INSERT INTO foo VALUES ('lisa')")?;
- assert!(called);
+ assert!(CALLED.load(Ordering::Relaxed));
Ok(())
}
@@ -398,4 +776,40 @@ mod test {
.unwrap_err();
Ok(())
}
+
+ #[test]
+ fn test_authorizer() -> Result<()> {
+ use super::{AuthAction, AuthContext, Authorization};
+
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (public TEXT, private TEXT)")
+ .unwrap();
+
+ let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
+ AuthAction::Read { column_name, .. } if column_name == "private" => {
+ Authorization::Ignore
+ }
+ AuthAction::DropTable { .. } => Authorization::Deny,
+ AuthAction::Pragma { .. } => panic!("shouldn't be called"),
+ _ => Authorization::Allow,
+ };
+
+ db.authorizer(Some(authorizer));
+ db.execute_batch(
+ "BEGIN TRANSACTION; INSERT INTO foo VALUES ('pub txt', 'priv txt'); COMMIT;",
+ )
+ .unwrap();
+ db.query_row_and_then("SELECT * FROM foo", [], |row| -> Result<()> {
+ assert_eq!(row.get::<_, String>("public")?, "pub txt");
+ assert!(row.get::<_, Option<String>>("private")?.is_none());
+ Ok(())
+ })
+ .unwrap();
+ db.execute_batch("DROP TABLE foo").unwrap_err();
+
+ db.authorizer(None::<fn(AuthContext<'_>) -> Authorization>);
+ db.execute_batch("PRAGMA user_version=1").unwrap(); // Disallowed by first authorizer, but it's now removed.
+
+ Ok(())
+ }
}
diff --git a/src/inner_connection.rs b/src/inner_connection.rs
index 0fbf5c1..0ea630e 100644
--- a/src/inner_connection.rs
+++ b/src/inner_connection.rs
@@ -13,7 +13,6 @@ use super::{Connection, InterruptHandle, OpenFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
-use crate::unlock_notify;
use crate::version::version_number;
pub struct InnerConnection {
@@ -33,9 +32,13 @@ pub struct InnerConnection {
pub free_update_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")]
pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
+ #[cfg(feature = "hooks")]
+ pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
owned: bool,
}
+unsafe impl Send for InnerConnection {}
+
impl InnerConnection {
#[allow(clippy::mutex_atomic)]
#[inline]
@@ -51,6 +54,8 @@ impl InnerConnection {
free_update_hook: None,
#[cfg(feature = "hooks")]
progress_handler: None,
+ #[cfg(feature = "hooks")]
+ authorizer: None,
owned,
}
}
@@ -60,8 +65,6 @@ impl InnerConnection {
flags: OpenFlags,
vfs: Option<&CStr>,
) -> Result<InnerConnection> {
- #[cfg(not(feature = "bundled"))]
- ensure_valid_sqlite_version();
ensure_safe_sqlite_threading_mode()?;
// Replicate the check for sane open flags from SQLite, because the check in
@@ -132,7 +135,7 @@ impl InnerConnection {
}
#[inline]
- pub fn decode_result(&mut self, code: c_int) -> Result<()> {
+ pub fn decode_result(&self, code: c_int) -> Result<()> {
unsafe { InnerConnection::decode_result_raw(self.db(), code) }
}
@@ -182,34 +185,31 @@ impl InnerConnection {
#[inline]
#[cfg(feature = "load_extension")]
- pub fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
- let r = unsafe { ffi::sqlite3_enable_load_extension(self.db, onoff) };
+ pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
+ let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
self.decode_result(r)
}
#[cfg(feature = "load_extension")]
- pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> {
+ pub unsafe fn load_extension(
+ &self,
+ dylib_path: &Path,
+ entry_point: Option<&str>,
+ ) -> Result<()> {
let dylib_str = super::path_to_cstring(dylib_path)?;
- unsafe {
- let mut errmsg: *mut c_char = ptr::null_mut();
- let r = if let Some(entry_point) = entry_point {
- let c_entry = crate::str_to_cstring(entry_point)?;
- ffi::sqlite3_load_extension(
- self.db,
- dylib_str.as_ptr(),
- c_entry.as_ptr(),
- &mut errmsg,
- )
- } else {
- ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
- };
- if r == ffi::SQLITE_OK {
- Ok(())
- } else {
- let message = super::errmsg_to_string(errmsg);
- ffi::sqlite3_free(errmsg as *mut ::std::os::raw::c_void);
- Err(error_from_sqlite_code(r, Some(message)))
- }
+ let mut errmsg: *mut c_char = ptr::null_mut();
+ let r = if let Some(entry_point) = entry_point {
+ let c_entry = crate::str_to_cstring(entry_point)?;
+ ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry.as_ptr(), &mut errmsg)
+ } else {
+ ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg)
+ };
+ if r == ffi::SQLITE_OK {
+ Ok(())
+ } else {
+ let message = super::errmsg_to_string(errmsg);
+ ffi::sqlite3_free(errmsg.cast::<::std::os::raw::c_void>());
+ Err(error_from_sqlite_code(r, Some(message)))
}
}
@@ -222,35 +222,37 @@ 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();
+ #[cfg(not(feature = "unlock_notify"))]
let r = unsafe {
- if cfg!(feature = "unlock_notify") {
- let mut rc;
- loop {
- rc = ffi::sqlite3_prepare_v2(
- self.db(),
- c_sql,
- len,
- &mut c_stmt as *mut *mut ffi::sqlite3_stmt,
- &mut c_tail as *mut *const c_char,
- );
- if !unlock_notify::is_locked(self.db, rc) {
- break;
- }
- rc = unlock_notify::wait_for_unlock_notify(self.db);
- if rc != ffi::SQLITE_OK {
- break;
- }
- }
- rc
- } else {
- ffi::sqlite3_prepare_v2(
+ ffi::sqlite3_prepare_v2(
+ self.db(),
+ c_sql,
+ len,
+ &mut c_stmt as *mut *mut ffi::sqlite3_stmt,
+ &mut c_tail as *mut *const c_char,
+ )
+ };
+ #[cfg(feature = "unlock_notify")]
+ let r = unsafe {
+ use crate::unlock_notify;
+ let mut rc;
+ loop {
+ rc = ffi::sqlite3_prepare_v2(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
- )
+ );
+ if !unlock_notify::is_locked(self.db, rc) {
+ break;
+ }
+ rc = unlock_notify::wait_for_unlock_notify(self.db);
+ if rc != ffi::SQLITE_OK {
+ break;
+ }
}
+ rc
};
// If there is an error, *ppStmt is set to NULL.
self.decode_result(r)?;
@@ -274,7 +276,7 @@ impl InnerConnection {
}
#[inline]
- pub fn changes(&mut self) -> usize {
+ pub fn changes(&self) -> usize {
unsafe { ffi::sqlite3_changes(self.db()) as usize }
}
@@ -298,6 +300,11 @@ impl InnerConnection {
false
}
+ #[cfg(feature = "modern_sqlite")] // 3.10.0
+ pub fn cache_flush(&mut self) -> Result<()> {
+ crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
+ }
+
#[cfg(not(feature = "hooks"))]
#[inline]
fn remove_hooks(&mut self) {}
@@ -319,55 +326,6 @@ impl Drop for InnerConnection {
}
}
-#[cfg(not(feature = "bundled"))]
-static SQLITE_VERSION_CHECK: std::sync::Once = std::sync::Once::new();
-#[cfg(not(feature = "bundled"))]
-pub static BYPASS_VERSION_CHECK: AtomicBool = AtomicBool::new(false);
-
-#[cfg(not(feature = "bundled"))]
-fn ensure_valid_sqlite_version() {
- use crate::version::version;
-
- SQLITE_VERSION_CHECK.call_once(|| {
- let version_number = version_number();
-
- // Check our hard floor.
- if version_number < 3_006_008 {
- panic!("rusqlite requires SQLite 3.6.8 or newer");
- }
-
- // Check that the major version number for runtime and buildtime match.
- let buildtime_major = ffi::SQLITE_VERSION_NUMBER / 1_000_000;
- let runtime_major = version_number / 1_000_000;
- if buildtime_major != runtime_major {
- panic!(
- "rusqlite was built against SQLite {} but is running with SQLite {}",
- str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
- version()
- );
- }
-
- if BYPASS_VERSION_CHECK.load(Ordering::Relaxed) {
- return;
- }
-
- // Check that the runtime version number is compatible with the version number
- // we found at build-time.
- if version_number < ffi::SQLITE_VERSION_NUMBER {
- panic!(
- "\
-rusqlite was built against SQLite {} but the runtime SQLite version is {}. To fix this, either:
-* Recompile rusqlite and link against the SQLite version you are using at runtime, or
-* Call rusqlite::bypass_sqlite_version_check() prior to your first connection attempt. Doing this
- means you're sure everything will work correctly even though the runtime version is older than
- the version we found at build time.",
- str::from_utf8(ffi::SQLITE_VERSION).unwrap(),
- version()
- );
- }
- });
-}
-
#[cfg(not(any(target_arch = "wasm32")))]
static SQLITE_INIT: std::sync::Once = std::sync::Once::new();
@@ -424,15 +382,13 @@ fn ensure_safe_sqlite_threading_mode() -> Result<()> {
}
unsafe {
- if ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) != ffi::SQLITE_OK || ffi::sqlite3_initialize() != ffi::SQLITE_OK {
- panic!(
+ assert!(ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) == ffi::SQLITE_OK && ffi::sqlite3_initialize() == ffi::SQLITE_OK,
"Could not ensure safe initialization of SQLite.\n\
To fix this, either:\n\
* Upgrade SQLite to at least version 3.7.0\n\
* Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call\n\
rusqlite::bypass_sqlite_initialization() prior to your first connection attempt."
);
- }
}
});
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index 926cad3..1fa7a33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,7 @@
//! }
//! ```
#![warn(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
pub use libsqlite3_sys as ffi;
@@ -73,8 +74,6 @@ pub use crate::cache::CachedStatement;
pub use crate::column::Column;
pub use crate::error::Error;
pub use crate::ffi::ErrorCode;
-#[cfg(feature = "hooks")]
-pub use crate::hooks::Action;
#[cfg(feature = "load_extension")]
pub use crate::load_extension_guard::LoadExtensionGuard;
pub use crate::params::{params_from_iter, Params, ParamsFromIter};
@@ -84,27 +83,32 @@ pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBe
pub use crate::types::ToSql;
pub use crate::version::*;
-#[macro_use]
mod error;
#[cfg(feature = "backup")]
+#[cfg_attr(docsrs, doc(cfg(feature = "backup")))]
pub mod backup;
#[cfg(feature = "blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
pub mod blob;
mod busy;
mod cache;
#[cfg(feature = "collation")]
+#[cfg_attr(docsrs, doc(cfg(feature = "collation")))]
mod collation;
mod column;
pub mod config;
#[cfg(any(feature = "functions", feature = "vtab"))]
mod context;
#[cfg(feature = "functions")]
+#[cfg_attr(docsrs, doc(cfg(feature = "functions")))]
pub mod functions;
#[cfg(feature = "hooks")]
-mod hooks;
+#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
+pub mod hooks;
mod inner_connection;
#[cfg(feature = "limits")]
+#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub mod limits;
#[cfg(feature = "load_extension")]
mod load_extension_guard;
@@ -113,15 +117,19 @@ mod pragma;
mod raw_statement;
mod row;
#[cfg(feature = "session")]
+#[cfg_attr(docsrs, doc(cfg(feature = "session")))]
pub mod session;
mod statement;
#[cfg(feature = "trace")]
+#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub mod trace;
mod transaction;
pub mod types;
+#[cfg(feature = "unlock_notify")]
mod unlock_notify;
mod version;
#[cfg(feature = "vtab")]
+#[cfg_attr(docsrs, doc(cfg(feature = "vtab")))]
pub mod vtab;
pub(crate) mod util;
@@ -152,9 +160,11 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
/// }
///
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
-/// conn.execute("INSERT INTO person (name, age_in_years, data)
+/// conn.execute(
+/// "INSERT INTO person (name, age_in_years, data)
/// VALUES (?1, ?2, ?3)",
-/// params![person.name, person.age_in_years, person.data])?;
+/// params![person.name, person.age_in_years, person.data],
+/// )?;
/// Ok(())
/// }
/// ```
@@ -186,11 +196,11 @@ macro_rules! params {
/// conn.execute(
/// "INSERT INTO person (name, age_in_years, data)
/// VALUES (:name, :age, :data)",
-/// named_params!{
+/// named_params! {
/// ":name": person.name,
/// ":age": person.age_in_years,
/// ":data": person.data,
-/// }
+/// },
/// )?;
/// Ok(())
/// }
@@ -248,10 +258,10 @@ fn str_to_cstring(s: &str) -> Result<SmallCString> {
fn str_for_sqlite(s: &[u8]) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?;
let (ptr, dtor_info) = if len != 0 {
- (s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
+ (s.as_ptr().cast::<c_char>(), ffi::SQLITE_TRANSIENT())
} else {
// Return a pointer guaranteed to live forever
- ("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
+ ("".as_ptr().cast::<c_char>(), ffi::SQLITE_STATIC())
};
Ok((ptr, len, dtor_info))
}
@@ -310,7 +320,7 @@ pub const TEMP_DB: DatabaseName<'static> = DatabaseName::Temp;
))]
impl DatabaseName<'_> {
#[inline]
- fn to_cstring(&self) -> Result<util::SmallCString> {
+ fn as_cstring(&self) -> Result<util::SmallCString> {
use self::DatabaseName::{Attached, Main, Temp};
match *self {
Main => str_to_cstring("main"),
@@ -440,7 +450,7 @@ impl Connection {
///
/// # Failure
///
- /// Will return `Err` if vfs` cannot be converted to a C-compatible
+ /// Will return `Err` if `vfs` cannot be converted to a C-compatible
/// string or if the underlying SQLite open call fails.
#[inline]
pub fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: &str) -> Result<Connection> {
@@ -455,10 +465,11 @@ impl Connection {
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
/// fn create_tables(conn: &Connection) -> Result<()> {
- /// conn.execute_batch("BEGIN;
- /// CREATE TABLE foo(x INTEGER);
- /// CREATE TABLE bar(y TEXT);
- /// COMMIT;",
+ /// conn.execute_batch(
+ /// "BEGIN;
+ /// CREATE TABLE foo(x INTEGER);
+ /// CREATE TABLE bar(y TEXT);
+ /// COMMIT;",
/// )
/// }
/// ```
@@ -506,9 +517,12 @@ impl Connection {
/// ### With positional params of varying types
///
/// ```rust,no_run
- /// # use rusqlite::{Connection};
+ /// # use rusqlite::{params, Connection};
/// fn update_rows(conn: &Connection) {
- /// match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?", [1i32]) {
+ /// match conn.execute(
+ /// "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+ /// params![1i32, 1.5f64],
+ /// ) {
/// Ok(updated) => println!("{} rows were updated", updated),
/// Err(err) => println!("update failed: {}", err),
/// }
@@ -537,6 +551,16 @@ impl Connection {
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
}
+ /// Returns the path to the database file, if one exists and is known.
+ ///
+ /// Note that in some cases [PRAGMA
+ /// database_list](https://sqlite.org/pragma.html#pragma_database_list) is
+ /// likely to be more robust.
+ #[inline]
+ pub fn path(&self) -> Option<&Path> {
+ self.path.as_deref()
+ }
+
/// Convenience method to prepare and execute a single SQL statement with
/// named parameter(s).
///
@@ -662,7 +686,7 @@ impl Connection {
stmt.check_no_tail()?;
let mut rows = stmt.query(params)?;
- rows.get_expected_row().map_err(E::from).and_then(|r| f(&r))
+ rows.get_expected_row().map_err(E::from).and_then(f)
}
/// Prepare a SQL statement for execution.
@@ -704,69 +728,119 @@ impl Connection {
r.map_err(move |err| (self, err))
}
- /// `feature = "load_extension"` Enable loading of SQLite extensions.
- /// Strongly consider using `LoadExtensionGuard` instead of this function.
+ /// Enable loading of SQLite extensions from both SQL queries and Rust.
///
- /// ## Example
+ /// You must call [`Connection::load_extension_disable`] when you're
+ /// finished loading extensions (failure to call it can lead to bad things,
+ /// see "Safety"), so you should strongly consider using
+ /// [`LoadExtensionGuard`] instead of this function, automatically disables
+ /// extension loading when it goes out of scope.
+ ///
+ /// # Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result};
- /// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
- /// conn.load_extension_enable()?;
- /// conn.load_extension(Path::new("my_sqlite_extension"), None)?;
- /// conn.load_extension_disable()
+ /// // Safety: We fully trust the loaded extension and execute no untrusted SQL
+ /// // while extension loading is enabled.
+ /// unsafe {
+ /// conn.load_extension_enable()?;
+ /// let r = conn.load_extension("my/trusted/extension", None);
+ /// conn.load_extension_disable()?;
+ /// r
+ /// }
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ ///
+ /// # Safety
+ ///
+ /// TLDR: Don't execute any untrusted queries between this call and
+ /// [`Connection::load_extension_disable`].
+ ///
+ /// Perhaps surprisingly, this function does not only allow the use of
+ /// [`Connection::load_extension`] from Rust, but it also allows SQL queries
+ /// to perform [the same operation][loadext]. For example, in the period
+ /// between `load_extension_enable` and `load_extension_disable`, the
+ /// following operation will load and call some function in some dynamic
+ /// library:
+ ///
+ /// ```sql
+ /// SELECT load_extension('why_is_this_possible.dll', 'dubious_func');
+ /// ```
+ ///
+ /// This means that while this is enabled a carefully crafted SQL query can
+ /// be used to escalate a SQL injection attack into code execution.
+ ///
+ /// Safely using this function requires that you trust all SQL queries run
+ /// between when it is called, and when loading is disabled (by
+ /// [`Connection::load_extension_disable`]).
+ ///
+ /// [loadext]: https://www.sqlite.org/lang_corefunc.html#load_extension
#[cfg(feature = "load_extension")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
- pub fn load_extension_enable(&self) -> Result<()> {
+ pub unsafe fn load_extension_enable(&self) -> Result<()> {
self.db.borrow_mut().enable_load_extension(1)
}
- /// `feature = "load_extension"` Disable loading of SQLite extensions.
+ /// Disable loading of SQLite extensions.
///
- /// See `load_extension_enable` for an example.
+ /// See [`Connection::load_extension_enable`] for an example.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
pub fn load_extension_disable(&self) -> Result<()> {
- self.db.borrow_mut().enable_load_extension(0)
+ // It's always safe to turn off extension loading.
+ unsafe { self.db.borrow_mut().enable_load_extension(0) }
}
- /// `feature = "load_extension"` Load the SQLite extension at `dylib_path`.
- /// `dylib_path` is passed through to `sqlite3_load_extension`, which may
- /// attempt OS-specific modifications if the file cannot be loaded directly.
+ /// Load the SQLite extension at `dylib_path`. `dylib_path` is passed
+ /// through to `sqlite3_load_extension`, which may attempt OS-specific
+ /// modifications if the file cannot be loaded directly (for example
+ /// converting `"some/ext"` to `"some/ext.so"`, `"some\\ext.dll"`, ...).
///
- /// If `entry_point` is `None`, SQLite will attempt to find the entry
- /// point. If it is not `None`, the entry point will be passed through
- /// to `sqlite3_load_extension`.
+ /// If `entry_point` is `None`, SQLite will attempt to find the entry point.
+ /// If it is not `None`, the entry point will be passed through to
+ /// `sqlite3_load_extension`.
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
- /// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
- /// let _guard = LoadExtensionGuard::new(conn)?;
- ///
- /// conn.load_extension("my_sqlite_extension", None)
+ /// // Safety: we don't execute any SQL statements while
+ /// // extension loading is enabled.
+ /// let _guard = unsafe { LoadExtensionGuard::new(conn)? };
+ /// // Safety: `my_sqlite_extension` is highly trustworthy.
+ /// unsafe { conn.load_extension("my_sqlite_extension", None) }
/// }
/// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
+ ///
+ /// # Safety
+ ///
+ /// This is equivalent to performing a `dlopen`/`LoadLibrary` on a shared
+ /// library, and calling a function inside, and thus requires that you trust
+ /// the library that you're loading.
+ ///
+ /// That is to say: to safely use this, the code in the extension must be
+ /// sound, trusted, correctly use the SQLite APIs, and not contain any
+ /// memory or thread safety errors.
#[cfg(feature = "load_extension")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
#[inline]
- pub fn load_extension<P: AsRef<Path>>(
+ pub unsafe fn load_extension<P: AsRef<Path>>(
&self,
dylib_path: P,
entry_point: Option<&str>,
@@ -822,7 +896,7 @@ impl Connection {
#[inline]
fn decode_result(&self, code: c_int) -> Result<()> {
- self.db.borrow_mut().decode_result(code)
+ self.db.borrow().decode_result(code)
}
/// Return the number of rows modified, inserted or deleted by the most
@@ -830,7 +904,7 @@ impl Connection {
/// connection.
#[inline]
fn changes(&self) -> usize {
- self.db.borrow_mut().changes()
+ self.db.borrow().changes()
}
/// Test for auto-commit mode.
@@ -843,9 +917,17 @@ impl Connection {
/// Determine if all associated prepared statements have been reset.
#[inline]
#[cfg(feature = "modern_sqlite")] // 3.8.6
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn is_busy(&self) -> bool {
self.db.borrow().is_busy()
}
+
+ /// Flush caches to disk mid-transaction
+ #[cfg(feature = "modern_sqlite")] // 3.10.0
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
+ pub fn cache_flush(&self) -> Result<()> {
+ self.db.borrow_mut().cache_flush()
+ }
}
impl fmt::Debug for Connection {
@@ -945,6 +1027,8 @@ bitflags::bitflags! {
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
/// The database filename is not allowed to be a symbolic link.
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
+ /// Extended result codes.
+ const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
}
}
@@ -980,21 +1064,6 @@ pub unsafe fn bypass_sqlite_initialization() {
BYPASS_SQLITE_INIT.store(true, Ordering::Relaxed);
}
-/// rusqlite performs a one-time check that the runtime SQLite version is at
-/// least as new as the version of SQLite found when rusqlite was built.
-/// Bypassing this check may be dangerous; e.g., if you use features of SQLite
-/// that are not present in the runtime version.
-///
-/// # Safety
-///
-/// If you are sure the runtime version is compatible with the
-/// build-time version for your usage, you can bypass the version check by
-/// calling this function before your first connection attempt.
-pub unsafe fn bypass_sqlite_version_check() {
- #[cfg(not(feature = "bundled"))]
- inner_connection::BYPASS_VERSION_CHECK.store(true, Ordering::Relaxed);
-}
-
/// Allows interrupting a long-running computation.
pub struct InterruptHandle {
db_lock: Arc<Mutex<*mut ffi::sqlite3>>,
@@ -1016,7 +1085,7 @@ impl InterruptHandle {
#[cfg(feature = "modern_sqlite")] // 3.7.10
unsafe fn db_filename(db: *mut ffi::sqlite3) -> Option<PathBuf> {
- let db_name = DatabaseName::Main.to_cstring().unwrap();
+ let db_name = DatabaseName::Main.as_cstring().unwrap();
let db_filename = ffi::sqlite3_db_filename(db, db_name.as_ptr());
if db_filename.is_null() {
None
@@ -1054,7 +1123,7 @@ mod test {
ensure_sync::<InterruptHandle>();
}
- pub fn checked_memory_handle() -> Connection {
+ fn checked_memory_handle() -> Connection {
Connection::open_in_memory().unwrap()
}
@@ -1134,7 +1203,7 @@ mod test {
fn test_open_failure() {
let filename = "no_such_file.db";
let result = Connection::open_with_flags(filename, OpenFlags::SQLITE_OPEN_READ_ONLY);
- assert!(!result.is_ok());
+ assert!(result.is_err());
let err = result.err().unwrap();
if let Error::SqliteFailure(e, Some(msg)) = err {
assert_eq!(ErrorCode::CannotOpen, e.code);
@@ -1182,7 +1251,7 @@ mod test {
#[test]
fn test_close_retry() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
// force the DB to be busy by preparing a statement; this must be done at the
// FFI level to allow us to call .close() without dropping the prepared
@@ -1235,7 +1304,7 @@ mod test {
#[test]
fn test_execute_batch() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1253,7 +1322,7 @@ mod test {
#[test]
fn test_execute() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER)")?;
assert_eq!(1, db.execute("INSERT INTO foo(x) VALUES (?)", [1i32])?);
@@ -1271,9 +1340,11 @@ mod test {
fn test_execute_select() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?", [1i32]).unwrap_err();
- if err != Error::ExecuteReturnedResults {
- panic!("Unexpected error: {}", err);
- }
+ assert!(
+ err == Error::ExecuteReturnedResults,
+ "Unexpected error: {}",
+ err
+ );
}
#[test]
@@ -1294,7 +1365,7 @@ mod test {
#[test]
fn test_prepare_column_names() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let stmt = db.prepare("SELECT * FROM foo")?;
@@ -1309,7 +1380,7 @@ mod test {
#[test]
fn test_prepare_execute() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
@@ -1330,7 +1401,7 @@ mod test {
#[test]
fn test_prepare_query() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)")?;
@@ -1365,7 +1436,7 @@ mod test {
#[test]
fn test_query_map() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1384,7 +1455,7 @@ mod test {
#[test]
fn test_query_row() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
@@ -1413,7 +1484,7 @@ mod test {
#[test]
fn test_optional() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let result: Result<i64> = db.query_row("SELECT 1 WHERE 0 <> 0", [], |r| r.get(0));
let result = result.optional();
@@ -1437,7 +1508,7 @@ mod test {
#[test]
fn test_pragma_query_row() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
assert_eq!(
"memory",
@@ -1452,7 +1523,7 @@ mod test {
#[test]
fn test_prepare_failures() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
@@ -1462,7 +1533,7 @@ mod test {
#[test]
fn test_last_insert_rowid() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
db.execute_batch("INSERT INTO foo DEFAULT VALUES")?;
@@ -1477,18 +1548,19 @@ mod test {
}
#[test]
- fn test_is_autocommit() {
- let db = checked_memory_handle();
+ fn test_is_autocommit() -> Result<()> {
+ let db = Connection::open_in_memory()?;
assert!(
db.is_autocommit(),
"autocommit expected to be active by default"
);
+ Ok(())
}
#[test]
#[cfg(feature = "modern_sqlite")]
fn test_is_busy() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
assert!(!db.is_busy());
let mut stmt = db.prepare("PRAGMA schema_version")?;
assert!(!db.is_busy());
@@ -1505,7 +1577,7 @@ mod test {
#[test]
fn test_statement_debugging() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let query = "SELECT 12345";
let stmt = db.prepare(query)?;
@@ -1524,7 +1596,7 @@ mod test {
#[cfg(not(feature = "modern_sqlite"))]
fn check_extended_code(_extended_code: c_int) {}
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x NOT NULL)")?;
let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", []);
@@ -1553,7 +1625,7 @@ mod test {
#[test]
#[cfg(feature = "functions")]
fn test_interrupt() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let interrupt_handle = db.get_interrupt_handle();
@@ -1601,7 +1673,7 @@ mod test {
#[test]
fn test_get_raw() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(i, x);")?;
let vals = ["foobar", "1234", "qwerty"];
let mut insert_stmt = db.prepare("INSERT INTO foo(i, x) VALUES(?, ?)")?;
@@ -1634,7 +1706,7 @@ mod test {
#[test]
fn test_from_handle() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let handle = unsafe { db.handle() };
{
let db = unsafe { Connection::from_handle(handle) }?;
@@ -1686,7 +1758,7 @@ mod test {
#[test]
fn test_query_and_then() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1706,7 +1778,7 @@ mod test {
#[test]
fn test_query_and_then_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1736,7 +1808,7 @@ mod test {
#[test]
fn test_query_and_then_custom_error() -> CustomResult<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1757,7 +1829,7 @@ mod test {
#[test]
fn test_query_and_then_custom_error_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1799,7 +1871,7 @@ mod test {
#[test]
fn test_query_row_and_then_custom_error() -> CustomResult<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1816,7 +1888,7 @@ mod test {
#[test]
fn test_query_row_and_then_custom_error_fails() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1853,7 +1925,7 @@ mod test {
#[test]
fn test_dynamic() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
@@ -1861,13 +1933,13 @@ mod test {
db.execute_batch(sql)?;
db.query_row("SELECT * FROM foo", [], |r| {
- assert_eq!(2, r.column_count());
+ assert_eq!(2, r.as_ref().column_count());
Ok(())
})
}
#[test]
fn test_dyn_box() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER);")?;
let b: Box<dyn ToSql> = Box::new(5);
db.execute("INSERT INTO foo VALUES(?)", [b])?;
@@ -1879,7 +1951,7 @@ mod test {
#[test]
fn test_params() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.query_row(
"SELECT
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
@@ -1900,7 +1972,7 @@ mod test {
#[test]
#[cfg(not(feature = "extra_check"))]
fn test_alter_table() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE x(t);")?;
// `execute_batch` should be used but `execute` should also work
db.execute("ALTER TABLE x RENAME TO y;", [])?;
@@ -1909,7 +1981,7 @@ mod test {
#[test]
fn test_batch() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
let sql = r"
CREATE TABLE tbl1 (col);
CREATE TABLE tbl2 (col);
@@ -1923,9 +1995,9 @@ mod test {
}
#[test]
- #[cfg(feature = "bundled")] // SQLite >= 3.35.0
+ #[cfg(all(feature = "bundled", not(feature = "bundled-sqlcipher")))] // SQLite >= 3.35.0
fn test_returning() -> Result<()> {
- let db = checked_memory_handle();
+ let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)")?;
let row_id =
db.query_row::<i64, _, _>("INSERT INTO foo DEFAULT VALUES RETURNING ROWID", [], |r| {
@@ -1934,4 +2006,11 @@ mod test {
assert_eq!(row_id, 1);
Ok(())
}
+
+ #[test]
+ #[cfg(feature = "modern_sqlite")]
+ fn test_cache_flush() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ db.cache_flush()
+ }
}
diff --git a/src/limits.rs b/src/limits.rs
index cacaa90..93e0bb0 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -1,23 +1,63 @@
-//! `feature = "limits"` Run-Time Limits
+//! Run-Time Limits
+use crate::{ffi, Connection};
use std::os::raw::c_int;
-use crate::ffi;
-pub use crate::ffi::Limit;
-
-use crate::Connection;
+/// Run-Time limit categories, for use with [`Connection::limit`] and
+/// [`Connection::set_limit`].
+///
+/// See the official documentation for more information:
+/// - <https://www.sqlite.org/c3ref/c_limit_attached.html>
+/// - <https://www.sqlite.org/limits.html>
+#[repr(i32)]
+#[non_exhaustive]
+#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
+#[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
+pub enum Limit {
+ /// The maximum size of any string or BLOB or table row, in bytes.
+ SQLITE_LIMIT_LENGTH = ffi::SQLITE_LIMIT_LENGTH,
+ /// The maximum length of an SQL statement, in bytes.
+ SQLITE_LIMIT_SQL_LENGTH = ffi::SQLITE_LIMIT_SQL_LENGTH,
+ /// The maximum number of columns in a table definition or in the result set
+ /// of a SELECT or the maximum number of columns in an index or in an
+ /// ORDER BY or GROUP BY clause.
+ SQLITE_LIMIT_COLUMN = ffi::SQLITE_LIMIT_COLUMN,
+ /// The maximum depth of the parse tree on any expression.
+ SQLITE_LIMIT_EXPR_DEPTH = ffi::SQLITE_LIMIT_EXPR_DEPTH,
+ /// The maximum number of terms in a compound SELECT statement.
+ SQLITE_LIMIT_COMPOUND_SELECT = ffi::SQLITE_LIMIT_COMPOUND_SELECT,
+ /// The maximum number of instructions in a virtual machine program used to
+ /// implement an SQL statement.
+ SQLITE_LIMIT_VDBE_OP = ffi::SQLITE_LIMIT_VDBE_OP,
+ /// The maximum number of arguments on a function.
+ SQLITE_LIMIT_FUNCTION_ARG = ffi::SQLITE_LIMIT_FUNCTION_ARG,
+ /// The maximum number of attached databases.
+ SQLITE_LIMIT_ATTACHED = ffi::SQLITE_LIMIT_ATTACHED,
+ /// The maximum length of the pattern argument to the LIKE or GLOB
+ /// operators.
+ SQLITE_LIMIT_LIKE_PATTERN_LENGTH = ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH,
+ /// The maximum index number of any parameter in an SQL statement.
+ SQLITE_LIMIT_VARIABLE_NUMBER = ffi::SQLITE_LIMIT_VARIABLE_NUMBER,
+ /// The maximum depth of recursion for triggers.
+ SQLITE_LIMIT_TRIGGER_DEPTH = 10,
+ /// The maximum number of auxiliary worker threads that a single prepared
+ /// statement may start.
+ SQLITE_LIMIT_WORKER_THREADS = 11,
+}
impl Connection {
- /// `feature = "limits"` Returns the current value of a limit.
+ /// Returns the current value of a [`Limit`].
#[inline]
+ #[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub fn limit(&self, limit: Limit) -> i32 {
let c = self.db.borrow();
unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, -1) }
}
- /// `feature = "limits"` Changes the limit to `new_val`, returning the prior
+ /// Changes the [`Limit`] to `new_val`, returning the prior
/// value of the limit.
#[inline]
+ #[cfg_attr(docsrs, doc(cfg(feature = "limits")))]
pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32 {
let c = self.db.borrow_mut();
unsafe { ffi::sqlite3_limit(c.db(), limit as c_int, new_val) }
@@ -26,10 +66,64 @@ impl Connection {
#[cfg(test)]
mod test {
- use crate::ffi::Limit;
+ use super::*;
use crate::{Connection, Result};
#[test]
+ fn test_limit_values() {
+ assert_eq!(
+ Limit::SQLITE_LIMIT_LENGTH as i32,
+ ffi::SQLITE_LIMIT_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_SQL_LENGTH as i32,
+ ffi::SQLITE_LIMIT_SQL_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_COLUMN as i32,
+ ffi::SQLITE_LIMIT_COLUMN as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_EXPR_DEPTH as i32,
+ ffi::SQLITE_LIMIT_EXPR_DEPTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_COMPOUND_SELECT as i32,
+ ffi::SQLITE_LIMIT_COMPOUND_SELECT as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_VDBE_OP as i32,
+ ffi::SQLITE_LIMIT_VDBE_OP as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_FUNCTION_ARG as i32,
+ ffi::SQLITE_LIMIT_FUNCTION_ARG as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_ATTACHED as i32,
+ ffi::SQLITE_LIMIT_ATTACHED as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
+ ffi::SQLITE_LIMIT_LIKE_PATTERN_LENGTH as i32,
+ );
+ assert_eq!(
+ Limit::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
+ ffi::SQLITE_LIMIT_VARIABLE_NUMBER as i32,
+ );
+ #[cfg(feature = "bundled")]
+ assert_eq!(
+ Limit::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
+ ffi::SQLITE_LIMIT_TRIGGER_DEPTH as i32,
+ );
+ #[cfg(feature = "bundled")]
+ assert_eq!(
+ Limit::SQLITE_LIMIT_WORKER_THREADS as i32,
+ ffi::SQLITE_LIMIT_WORKER_THREADS as i32,
+ );
+ }
+
+ #[test]
fn test_limit() -> Result<()> {
let db = Connection::open_in_memory()?;
db.set_limit(Limit::SQLITE_LIMIT_LENGTH, 1024);
diff --git a/src/load_extension_guard.rs b/src/load_extension_guard.rs
index b4c38c2..deed3b4 100644
--- a/src/load_extension_guard.rs
+++ b/src/load_extension_guard.rs
@@ -1,7 +1,6 @@
use crate::{Connection, Result};
-/// `feature = "load_extension"` RAII guard temporarily enabling SQLite
-/// extensions to be loaded.
+/// RAII guard temporarily enabling SQLite extensions to be loaded.
///
/// ## Example
///
@@ -9,11 +8,13 @@ use crate::{Connection, Result};
/// # use rusqlite::{Connection, Result, LoadExtensionGuard};
/// # use std::path::{Path};
/// fn load_my_extension(conn: &Connection) -> Result<()> {
-/// let _guard = LoadExtensionGuard::new(conn)?;
-///
-/// conn.load_extension(Path::new("my_sqlite_extension"), None)
+/// unsafe {
+/// let _guard = LoadExtensionGuard::new(conn)?;
+/// conn.load_extension("trusted/sqlite/extension", None)
+/// }
/// }
/// ```
+#[cfg_attr(docsrs, doc(cfg(feature = "load_extension")))]
pub struct LoadExtensionGuard<'conn> {
conn: &'conn Connection,
}
@@ -22,8 +23,15 @@ impl LoadExtensionGuard<'_> {
/// Attempt to enable loading extensions. Loading extensions will be
/// disabled when this guard goes out of scope. Cannot be meaningfully
/// nested.
+ ///
+ /// # Safety
+ ///
+ /// You must not run untrusted queries while extension loading is enabled.
+ ///
+ /// See the safety comment on [`Connection::load_extension_enable`] for more
+ /// details.
#[inline]
- pub fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
+ pub unsafe fn new(conn: &Connection) -> Result<LoadExtensionGuard<'_>> {
conn.load_extension_enable()
.map(|_| LoadExtensionGuard { conn })
}
diff --git a/src/params.rs b/src/params.rs
index 88fce97..54aa571 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -99,7 +99,7 @@ use sealed::Sealed;
///
/// - As a slice of `&[(&str, &dyn ToSql)]`. This is what essentially all of
/// these boil down to in the end, conceptually at least. In theory you can
-/// pass this as `stmt.
+/// pass this as `stmt`.
///
/// - As array references, similar to the positional params. This looks like
/// `thing.query(&[(":foo", &1i32), (":bar", &2i32)])` or
@@ -116,7 +116,7 @@ use sealed::Sealed;
/// fn insert(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
/// // Using `rusqlite::params!`:
-/// stmt.execute(named_params!{ ":key": "one", ":val": 2 })?;
+/// stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
/// // Alternatively:
/// stmt.execute(&[(":key", "three"), (":val", "four")])?;
/// // Or:
@@ -169,8 +169,8 @@ pub trait Params: Sealed {
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API.
-impl Sealed for [&dyn ToSql; 0] {}
-impl Params for [&dyn ToSql; 0] {
+impl Sealed for [&(dyn ToSql + Send + Sync); 0] {}
+impl Params for [&(dyn ToSql + Send + Sync); 0] {
#[inline]
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
// Note: Can't just return `Ok(())` — `Statement::bind_parameters`
@@ -251,7 +251,7 @@ impl_for_array_ref!(
/// ## Basic usage
///
/// ```rust,no_run
-/// use rusqlite::{Connection, Result, params_from_iter};
+/// use rusqlite::{params_from_iter, Connection, Result};
/// use std::collections::BTreeSet;
///
/// fn query(conn: &Connection, ids: &BTreeSet<String>) -> Result<()> {
diff --git a/src/pragma.rs b/src/pragma.rs
index 16a6307..1c81c95 100644
--- a/src/pragma.rs
+++ b/src/pragma.rs
@@ -143,7 +143,7 @@ impl Sql {
if ch == quote {
self.buf.push(ch);
}
- self.buf.push(ch)
+ self.buf.push(ch);
}
self.buf.push(quote);
}
@@ -198,7 +198,7 @@ impl Connection {
let mut rows = stmt.query([])?;
while let Some(result_row) = rows.next()? {
let row = result_row;
- f(&row)?;
+ f(row)?;
}
Ok(())
}
@@ -212,15 +212,16 @@ impl Connection {
///
/// Prefer [PRAGMA function](https://sqlite.org/pragma.html#pragfunc) introduced in SQLite 3.20:
/// `SELECT * FROM pragma_table_info(?);`
- pub fn pragma<F>(
+ pub fn pragma<F, V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
+ pragma_value: V,
mut f: F,
) -> Result<()>
where
F: FnMut(&Row<'_>) -> Result<()>,
+ V: ToSql,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
@@ -228,13 +229,13 @@ impl Connection {
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.open_brace();
- sql.push_value(pragma_value)?;
+ sql.push_value(&pragma_value)?;
sql.close_brace();
let mut stmt = self.prepare(&sql)?;
let mut rows = stmt.query([])?;
while let Some(result_row) = rows.next()? {
let row = result_row;
- f(&row)?;
+ f(row)?;
}
Ok(())
}
@@ -243,34 +244,38 @@ impl Connection {
///
/// Some pragmas will return the updated value which cannot be retrieved
/// with this method.
- pub fn pragma_update(
+ pub fn pragma_update<V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
- ) -> Result<()> {
+ pragma_value: V,
+ ) -> Result<()>
+ where
+ V: ToSql,
+ {
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
// The argument may be either in parentheses
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
- sql.push_value(pragma_value)?;
+ sql.push_value(&pragma_value)?;
self.execute_batch(&sql)
}
/// Set a new value to `pragma_name` and return the updated value.
///
/// Only few pragmas automatically return the updated value.
- pub fn pragma_update_and_check<F, T>(
+ pub fn pragma_update_and_check<F, T, V>(
&self,
schema_name: Option<DatabaseName<'_>>,
pragma_name: &str,
- pragma_value: &dyn ToSql,
+ pragma_value: V,
f: F,
) -> Result<T>
where
F: FnOnce(&Row<'_>) -> Result<T>,
+ V: ToSql,
{
let mut sql = Sql::new();
sql.push_pragma(schema_name, pragma_name)?;
@@ -278,7 +283,7 @@ impl Connection {
// or it may be separated from the pragma name by an equal sign.
// The two syntaxes yield identical results.
sql.push_equal_sign();
- sql.push_value(pragma_value)?;
+ sql.push_value(&pragma_value)?;
self.query_row(&sql, [], f)
}
}
@@ -393,15 +398,26 @@ mod test {
#[test]
fn pragma_update() -> Result<()> {
let db = Connection::open_in_memory()?;
- db.pragma_update(None, "user_version", &1)
+ db.pragma_update(None, "user_version", 1)
}
#[test]
fn pragma_update_and_check() -> Result<()> {
let db = Connection::open_in_memory()?;
let journal_mode: String =
- db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get(0))?;
+ db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
assert_eq!("off", &journal_mode);
+ // Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
+ assert_eq!(
+ "off",
+ db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
+ .get::<_, String>(0))?,
+ );
+ let param: &dyn crate::ToSql = &"OFF";
+ assert_eq!(
+ "off",
+ db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
+ );
Ok(())
}
diff --git a/src/raw_statement.rs b/src/raw_statement.rs
index ef94d81..8e624dc 100644
--- a/src/raw_statement.rs
+++ b/src/raw_statement.rs
@@ -1,6 +1,6 @@
use super::ffi;
-use super::unlock_notify;
use super::StatementStatus;
+use crate::util::ParamIndexCache;
#[cfg(feature = "modern_sqlite")]
use crate::util::SqliteMallocString;
use std::ffi::CStr;
@@ -14,7 +14,7 @@ pub struct RawStatement {
ptr: *mut ffi::sqlite3_stmt,
tail: usize,
// Cached indices of named parameters, computed on the fly.
- cache: crate::util::ParamIndexCache,
+ cache: ParamIndexCache,
// Cached SQL (trimmed) that we use as the key when we're in the statement
// cache. This is None for statements which didn't come from the statement
// cache.
@@ -34,7 +34,7 @@ impl RawStatement {
RawStatement {
ptr: stmt,
tail,
- cache: Default::default(),
+ cache: ParamIndexCache::default(),
statement_cache_key: None,
}
}
@@ -101,25 +101,38 @@ impl RawStatement {
}
}
- #[cfg_attr(not(feature = "unlock_notify"), inline)]
+ #[inline]
+ #[cfg(not(feature = "unlock_notify"))]
+ pub fn step(&self) -> c_int {
+ unsafe { ffi::sqlite3_step(self.ptr) }
+ }
+
+ #[cfg(feature = "unlock_notify")]
pub fn step(&self) -> c_int {
- if cfg!(feature = "unlock_notify") {
- let db = unsafe { ffi::sqlite3_db_handle(self.ptr) };
- let mut rc;
- loop {
- rc = unsafe { ffi::sqlite3_step(self.ptr) };
- if unsafe { !unlock_notify::is_locked(db, rc) } {
- break;
+ use crate::unlock_notify;
+ let mut db = core::ptr::null_mut::<ffi::sqlite3>();
+ loop {
+ unsafe {
+ let mut rc = ffi::sqlite3_step(self.ptr);
+ // Bail out early for success and errors unrelated to locking. We
+ // still need check `is_locked` after this, but checking now lets us
+ // avoid one or two (admittedly cheap) calls into SQLite that we
+ // don't need to make.
+ if (rc & 0xff) != ffi::SQLITE_LOCKED {
+ break rc;
}
- rc = unsafe { unlock_notify::wait_for_unlock_notify(db) };
+ if db.is_null() {
+ db = ffi::sqlite3_db_handle(self.ptr);
+ }
+ if !unlock_notify::is_locked(db, rc) {
+ break rc;
+ }
+ rc = unlock_notify::wait_for_unlock_notify(db);
if rc != ffi::SQLITE_OK {
- break;
+ break rc;
}
self.reset();
}
- rc
- } else {
- unsafe { ffi::sqlite3_step(self.ptr) }
}
}
diff --git a/src/row.rs b/src/row.rs
index 137f13d..c766e50 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -78,6 +78,12 @@ impl<'stmt> Rows<'stmt> {
{
AndThenRows { rows: self, map: f }
}
+
+ /// Give access to the underlying statement
+ #[must_use]
+ pub fn as_ref(&self) -> Option<&Statement<'stmt>> {
+ self.stmt
+ }
}
impl<'stmt> Rows<'stmt> {
@@ -151,7 +157,7 @@ where
self.rows
.next()
.transpose()
- .map(|row_result| row_result.and_then(|row| (map)(&row)))
+ .map(|row_result| row_result.and_then(map))
}
}
@@ -176,13 +182,13 @@ where
self.rows
.next()
.transpose()
- .map(|row_result| row_result.map_err(E::from).and_then(|row| (map)(&row)))
+ .map(|row_result| row_result.map_err(E::from).and_then(map))
}
}
/// `FallibleStreamingIterator` differs from the standard library's `Iterator`
/// in two ways:
-/// * each call to `next` (sqlite3_step) can fail.
+/// * each call to `next` (`sqlite3_step`) can fail.
/// * returned `Row` is valid until `next` is called again or `Statement` is
/// reset or finalized.
///
@@ -204,8 +210,8 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
#[inline]
fn advance(&mut self) -> Result<()> {
- match self.stmt {
- Some(ref stmt) => match stmt.step() {
+ if let Some(stmt) = self.stmt {
+ match stmt.step() {
Ok(true) => {
self.row = Some(Row { stmt });
Ok(())
@@ -220,11 +226,10 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
self.row = None;
Err(e)
}
- },
- None => {
- self.row = None;
- Ok(())
}
+ } else {
+ self.row = None;
+ Ok(())
}
}
@@ -283,20 +288,11 @@ impl<'stmt> Row<'stmt> {
),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => {
- Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
+ Error::FromSqlConversionFailure(idx, value.data_type(), err)
+ }
+ FromSqlError::InvalidBlobSize { .. } => {
+ Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))
}
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(
- idx,
- self.stmt.column_name_unwrap(idx).into(),
- value.data_type(),
- ),
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(
- idx,
- self.stmt.column_name_unwrap(idx).into(),
- value.data_type(),
- ),
})
}
@@ -358,6 +354,12 @@ impl<'stmt> Row<'stmt> {
}
}
+impl<'stmt> AsRef<Statement<'stmt>> for Row<'stmt> {
+ fn as_ref(&self) -> &Statement<'stmt> {
+ self.stmt
+ }
+}
+
mod sealed {
/// This trait exists just to ensure that the only impls of `trait Params`
/// that are allowed are ones in this crate.
diff --git a/src/session.rs b/src/session.rs
index 4e4c354..b02d306 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -1,4 +1,4 @@
-//! `feature = "session"` [Session Extension](https://sqlite.org/sessionintro.html)
+//! [Session Extension](https://sqlite.org/sessionintro.html)
#![allow(non_camel_case_types)]
use std::ffi::CStr;
@@ -11,7 +11,7 @@ use std::slice::{from_raw_parts, from_raw_parts_mut};
use fallible_streaming_iterator::FallibleStreamingIterator;
-use crate::error::error_from_sqlite_code;
+use crate::error::{check, error_from_sqlite_code};
use crate::ffi;
use crate::hooks::Action;
use crate::types::ValueRef;
@@ -19,7 +19,7 @@ use crate::{errmsg_to_string, str_to_cstring, Connection, DatabaseName, Result};
// https://sqlite.org/session.html
-/// `feature = "session"` An instance of this object is a session that can be
+/// An instance of this object is a session that can be
/// used to record changes to a database.
pub struct Session<'conn> {
phantom: PhantomData<&'conn Connection>,
@@ -40,12 +40,12 @@ impl Session<'_> {
db: &'conn Connection,
name: DatabaseName<'_>,
) -> Result<Session<'conn>> {
- let name = name.to_cstring()?;
+ let name = name.as_cstring()?;
let db = db.db.borrow_mut().db;
let mut s: *mut ffi::sqlite3_session = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) });
+ check(unsafe { ffi::sqlite3session_create(db, name.as_ptr(), &mut s) })?;
Ok(Session {
phantom: PhantomData,
@@ -109,15 +109,14 @@ impl Session<'_> {
None
};
let table = table.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
- unsafe { check!(ffi::sqlite3session_attach(self.s, table)) };
- Ok(())
+ check(unsafe { ffi::sqlite3session_attach(self.s, table) })
}
/// Generate a Changeset
pub fn changeset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut cs: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) });
+ check(unsafe { ffi::sqlite3session_changeset(self.s, &mut n, &mut cs) })?;
Ok(Changeset { cs, n })
}
@@ -125,14 +124,13 @@ impl Session<'_> {
#[inline]
pub fn changeset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3session_changeset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Generate a Patchset
@@ -140,7 +138,7 @@ impl Session<'_> {
pub fn patchset(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut ps: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) });
+ check(unsafe { ffi::sqlite3session_patchset(self.s, &mut n, &mut ps) })?;
// TODO Validate: same struct
Ok(Changeset { cs: ps, n })
}
@@ -149,19 +147,18 @@ impl Session<'_> {
#[inline]
pub fn patchset_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3session_patchset_strm(
self.s,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Load the difference between tables.
pub fn diff(&mut self, from: DatabaseName<'_>, table: &str) -> Result<()> {
- let from = from.to_cstring()?;
+ let from = from.as_cstring()?;
let table = str_to_cstring(table)?;
let table = table.as_ptr();
unsafe {
@@ -223,23 +220,22 @@ impl Drop for Session<'_> {
}
}
-/// `feature = "session"` Invert a changeset
+/// Invert a changeset
#[inline]
pub fn invert_strm(input: &mut dyn Read, output: &mut dyn Write) -> Result<()> {
let input_ref = &input;
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_invert_strm(
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
-/// `feature = "session"` Combine two changesets
+/// Combine two changesets
#[inline]
pub fn concat_strm(
input_a: &mut dyn Read,
@@ -249,7 +245,7 @@ pub fn concat_strm(
let input_a_ref = &input_a;
let input_b_ref = &input_b;
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_concat_strm(
Some(x_input),
input_a_ref as *const &mut dyn Read as *mut c_void,
@@ -258,11 +254,10 @@ pub fn concat_strm(
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
-/// `feature = "session"` Changeset or Patchset
+/// Changeset or Patchset
pub struct Changeset {
cs: *mut c_void,
n: c_int,
@@ -274,9 +269,9 @@ impl Changeset {
pub fn invert(&self) -> Result<Changeset> {
let mut n = 0;
let mut cs = ptr::null_mut();
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_invert(self.n, self.cs, &mut n, &mut cs as *mut *mut _)
- });
+ })?;
Ok(Changeset { cs, n })
}
@@ -284,7 +279,7 @@ impl Changeset {
#[inline]
pub fn iter(&self) -> Result<ChangesetIter<'_>> {
let mut it = ptr::null_mut();
- check!(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) });
+ check(unsafe { ffi::sqlite3changeset_start(&mut it as *mut *mut _, self.n, self.cs) })?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -297,9 +292,9 @@ impl Changeset {
pub fn concat(a: &Changeset, b: &Changeset) -> Result<Changeset> {
let mut n = 0;
let mut cs = ptr::null_mut();
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_concat(a.n, a.cs, b.n, b.cs, &mut n, &mut cs as *mut *mut _)
- });
+ })?;
Ok(Changeset { cs, n })
}
}
@@ -313,7 +308,7 @@ impl Drop for Changeset {
}
}
-/// `feature = "session"` Cursor for iterating over the elements of a changeset
+/// Cursor for iterating over the elements of a changeset
/// or patchset.
pub struct ChangesetIter<'changeset> {
phantom: PhantomData<&'changeset Changeset>,
@@ -326,13 +321,13 @@ impl ChangesetIter<'_> {
#[inline]
pub fn start_strm<'input>(input: &&'input mut dyn Read) -> Result<ChangesetIter<'input>> {
let mut it = ptr::null_mut();
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changeset_start_strm(
&mut it as *mut *mut _,
Some(x_input),
input as *const &mut dyn Read as *mut c_void,
)
- });
+ })?;
Ok(ChangesetIter {
phantom: PhantomData,
it,
@@ -367,7 +362,7 @@ impl FallibleStreamingIterator for ChangesetIter<'_> {
}
}
-/// `feature = "session"`
+/// Operation
pub struct Operation<'item> {
table_name: &'item str,
number_of_columns: i32,
@@ -410,7 +405,7 @@ impl Drop for ChangesetIter<'_> {
}
}
-/// `feature = "session"` An item passed to a conflict-handler by
+/// An item passed to a conflict-handler by
/// [`Connection::apply`](crate::Connection::apply), or an item generated by
/// [`ChangesetIter::next`](ChangesetIter::next).
// TODO enum ? Delete, Insert, Update, ...
@@ -427,11 +422,11 @@ impl ChangesetItem {
pub fn conflict(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
- check!(ffi::sqlite3changeset_conflict(
+ check(ffi::sqlite3changeset_conflict(
self.it,
col as i32,
&mut p_value,
- ));
+ ))?;
Ok(ValueRef::from_value(p_value))
}
}
@@ -444,7 +439,7 @@ impl ChangesetItem {
pub fn fk_conflicts(&self) -> Result<i32> {
unsafe {
let mut p_out = 0;
- check!(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out));
+ check(ffi::sqlite3changeset_fk_conflicts(self.it, &mut p_out))?;
Ok(p_out)
}
}
@@ -457,7 +452,7 @@ impl ChangesetItem {
pub fn new_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
- check!(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value,));
+ check(ffi::sqlite3changeset_new(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
@@ -470,7 +465,7 @@ impl ChangesetItem {
pub fn old_value(&self, col: usize) -> Result<ValueRef<'_>> {
unsafe {
let mut p_value: *mut ffi::sqlite3_value = ptr::null_mut();
- check!(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value,));
+ check(ffi::sqlite3changeset_old(self.it, col as i32, &mut p_value))?;
Ok(ValueRef::from_value(p_value))
}
}
@@ -483,13 +478,13 @@ impl ChangesetItem {
let mut indirect = 0;
let tab = unsafe {
let mut pz_tab: *const c_char = ptr::null();
- check!(ffi::sqlite3changeset_op(
+ check(ffi::sqlite3changeset_op(
self.it,
&mut pz_tab,
&mut number_of_columns,
&mut code,
- &mut indirect
- ));
+ &mut indirect,
+ ))?;
CStr::from_ptr(pz_tab)
};
let table_name = tab.to_str()?;
@@ -507,17 +502,17 @@ impl ChangesetItem {
let mut number_of_columns = 0;
unsafe {
let mut pks: *mut c_uchar = ptr::null_mut();
- check!(ffi::sqlite3changeset_pk(
+ check(ffi::sqlite3changeset_pk(
self.it,
&mut pks,
- &mut number_of_columns
- ));
+ &mut number_of_columns,
+ ))?;
Ok(from_raw_parts(pks, number_of_columns as usize))
}
}
}
-/// `feature = "session"` Used to combine two or more changesets or
+/// Used to combine two or more changesets or
/// patchsets
pub struct Changegroup {
cg: *mut ffi::sqlite3_changegroup,
@@ -528,29 +523,27 @@ impl Changegroup {
#[inline]
pub fn new() -> Result<Self> {
let mut cg = ptr::null_mut();
- check!(unsafe { ffi::sqlite3changegroup_new(&mut cg) });
+ check(unsafe { ffi::sqlite3changegroup_new(&mut cg) })?;
Ok(Changegroup { cg })
}
/// Add a changeset
#[inline]
pub fn add(&mut self, cs: &Changeset) -> Result<()> {
- check!(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) });
- Ok(())
+ check(unsafe { ffi::sqlite3changegroup_add(self.cg, cs.n, cs.cs) })
}
/// Add a changeset read from `input` to this change group.
#[inline]
pub fn add_stream(&mut self, input: &mut dyn Read) -> Result<()> {
let input_ref = &input;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changegroup_add_strm(
self.cg,
Some(x_input),
input_ref as *const &mut dyn Read as *mut c_void,
)
- });
- Ok(())
+ })
}
/// Obtain a composite Changeset
@@ -558,7 +551,7 @@ impl Changegroup {
pub fn output(&mut self) -> Result<Changeset> {
let mut n = 0;
let mut output: *mut c_void = ptr::null_mut();
- check!(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) });
+ check(unsafe { ffi::sqlite3changegroup_output(self.cg, &mut n, &mut output) })?;
Ok(Changeset { cs: output, n })
}
@@ -566,14 +559,13 @@ impl Changegroup {
#[inline]
pub fn output_strm(&mut self, output: &mut dyn Write) -> Result<()> {
let output_ref = &output;
- check!(unsafe {
+ check(unsafe {
ffi::sqlite3changegroup_output_strm(
self.cg,
Some(x_output),
output_ref as *const &mut dyn Write as *mut c_void,
)
- });
- Ok(())
+ })
}
}
@@ -587,7 +579,7 @@ impl Drop for Changegroup {
}
impl Connection {
- /// `feature = "session"` Apply a changeset to a database
+ /// Apply a changeset to a database
pub fn apply<F, C>(&self, cs: &Changeset, filter: Option<F>, conflict: C) -> Result<()>
where
F: Fn(&str) -> bool + Send + RefUnwindSafe + 'static,
@@ -597,7 +589,7 @@ impl Connection {
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
- check!(unsafe {
+ check(unsafe {
if filtered {
ffi::sqlite3changeset_apply(
db,
@@ -617,11 +609,10 @@ impl Connection {
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
- });
- Ok(())
+ })
}
- /// `feature = "session"` Apply a changeset to a database
+ /// Apply a changeset to a database
pub fn apply_strm<F, C>(
&self,
input: &mut dyn Read,
@@ -637,7 +628,7 @@ impl Connection {
let filtered = filter.is_some();
let tuple = &mut (filter, conflict);
- check!(unsafe {
+ check(unsafe {
if filtered {
ffi::sqlite3changeset_apply_strm(
db,
@@ -657,12 +648,11 @@ impl Connection {
tuple as *mut (Option<F>, C) as *mut c_void,
)
}
- });
- Ok(())
+ })
}
}
-/// `feature = "session"` Constants passed to the conflict handler
+/// Constants passed to the conflict handler
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_CONFLICT) for details.
#[allow(missing_docs)]
#[repr(i32)]
@@ -690,7 +680,7 @@ impl From<i32> for ConflictType {
}
}
-/// `feature = "session"` Constants returned by the conflict handler
+/// Constants returned by the conflict handler
/// See [here](https://sqlite.org/session.html#SQLITE_CHANGESET_ABORT) for details.
#[allow(missing_docs)]
#[repr(i32)]
@@ -826,7 +816,7 @@ mod test {
assert_eq!("foo", op.table_name());
assert_eq!(1, op.number_of_columns());
assert_eq!(Action::SQLITE_INSERT, op.code());
- assert_eq!(false, op.indirect());
+ assert!(!op.indirect());
let pk = item.pk()?;
assert_eq!(&[1], pk);
diff --git a/src/statement.rs b/src/statement.rs
index 139f504..60abd90 100644
--- a/src/statement.rs
+++ b/src/statement.rs
@@ -55,7 +55,7 @@ impl Statement<'_> {
/// // The `rusqlite::named_params!` macro (like `params!`) is useful for heterogeneous
/// // sets of parameters (where all parameters are not the same type), or for queries
/// // with many (more than 32) statically known parameters.
- /// stmt.execute(named_params!{ ":key": "one", ":val": 2 })?;
+ /// stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
/// // However, named parameters can also be passed like:
/// stmt.execute(&[(":key", "three"), (":val", "four")])?;
/// // Or even: (note that a &T is required for the value type, currently)
@@ -135,8 +135,8 @@ impl Statement<'_> {
/// Execute the prepared statement, returning a handle to the resulting
/// rows.
///
- /// Due to lifetime restricts, the rows handle returned by `query` does not
- /// implement the `Iterator` trait. Consider using
+ /// Due to lifetime restrictions, the rows handle returned by `query` does
+ /// not implement the `Iterator` trait. Consider using
/// [`query_map`](Statement::query_map) or
/// [`query_and_then`](Statement::query_and_then) instead, which do.
///
@@ -208,7 +208,7 @@ impl Statement<'_> {
/// # use rusqlite::{Connection, Result, named_params};
/// fn query(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
- /// let mut rows = stmt.query(named_params!{ ":name": "one" })?;
+ /// let mut rows = stmt.query(named_params! { ":name": "one" })?;
/// while let Some(row) = rows.next()? {
/// // ...
/// }
@@ -346,13 +346,12 @@ impl Statement<'_> {
///
/// fn name_to_person(name: String) -> Result<Person> {
/// // ... check for valid name
- /// Ok(Person { name: name })
+ /// Ok(Person { name })
/// }
///
/// fn get_names(conn: &Connection) -> Result<Vec<Person>> {
/// let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
- /// let rows =
- /// stmt.query_and_then(&[(":id", "one")], |row| name_to_person(row.get(0)?))?;
+ /// let rows = stmt.query_and_then(&[(":id", "one")], |row| name_to_person(row.get(0)?))?;
///
/// let mut persons = Vec::new();
/// for person_result in rows {
@@ -453,7 +452,7 @@ impl Statement<'_> {
{
let mut rows = self.query(params)?;
- rows.get_expected_row().and_then(|r| f(&r))
+ rows.get_expected_row().and_then(f)
}
/// Convenience method to execute a query with named parameter(s) that is
@@ -718,7 +717,7 @@ impl Statement<'_> {
ffi::sqlite3_bind_blob(
ptr,
col as c_int,
- b.as_ptr() as *const c_void,
+ b.as_ptr().cast::<c_void>(),
length,
ffi::SQLITE_TRANSIENT(),
)
@@ -776,6 +775,7 @@ impl Statement<'_> {
/// Returns a string containing the SQL text of prepared statement with
/// bound parameters expanded.
#[cfg(feature = "modern_sqlite")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
pub fn expanded_sql(&self) -> Option<String> {
self.stmt
.expanded_sql()
@@ -875,7 +875,7 @@ impl Statement<'_> {
!text.is_null(),
"unexpected SQLITE_TEXT column type with NULL data"
);
- from_raw_parts(text as *const u8, len as usize)
+ from_raw_parts(text.cast::<u8>(), len as usize)
};
ValueRef::Text(s)
@@ -897,7 +897,7 @@ impl Statement<'_> {
!blob.is_null(),
"unexpected SQLITE_BLOB column type with NULL data"
);
- ValueRef::Blob(unsafe { from_raw_parts(blob as *const u8, len as usize) })
+ ValueRef::Blob(unsafe { from_raw_parts(blob.cast::<u8>(), len as usize) })
} else {
// The return value from sqlite3_column_blob() for a zero-length BLOB
// is a NULL pointer.
@@ -946,6 +946,10 @@ pub enum StatementStatus {
RePrepare = 5,
/// Equivalent to SQLITE_STMTSTATUS_RUN
Run = 6,
+ /// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
+ FilterMiss = 7,
+ /// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
+ FilterHit = 8,
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
MemUsed = 99,
}
diff --git a/src/trace.rs b/src/trace.rs
index 8e8fd3a..3932976 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -1,4 +1,4 @@
-//! `feature = "trace"` Tracing and profiling functions. Error and warning log.
+//! Tracing and profiling functions. Error and warning log.
use std::ffi::{CStr, CString};
use std::mem;
@@ -11,7 +11,7 @@ use super::ffi;
use crate::error::error_from_sqlite_code;
use crate::{Connection, Result};
-/// `feature = "trace"` Set up the process-wide SQLite error logging callback.
+/// Set up the process-wide SQLite error logging callback.
///
/// # Safety
///
@@ -31,19 +31,18 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
let s = String::from_utf8_lossy(c_slice);
- let _ = catch_unwind(|| callback(err, &s));
+ drop(catch_unwind(|| callback(err, &s)));
}
- let rc = match callback {
- Some(f) => ffi::sqlite3_config(
+ let rc = if let Some(f) = callback {
+ ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
f as *mut c_void,
- ),
- None => {
- let nullptr: *mut c_void = ptr::null_mut();
- ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
- }
+ )
+ } else {
+ let nullptr: *mut c_void = ptr::null_mut();
+ ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
};
if rc == ffi::SQLITE_OK {
@@ -53,7 +52,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
}
}
-/// `feature = "trace"` Write a message into the error log established by
+/// Write a message into the error log established by
/// `config_log`.
#[inline]
pub fn log(err_code: c_int, msg: &str) {
@@ -64,7 +63,7 @@ pub fn log(err_code: c_int, msg: &str) {
}
impl Connection {
- /// `feature = "trace"` Register or clear a callback function that can be
+ /// Register or clear a callback function that can be
/// used for tracing the execution of SQL statements.
///
/// Prepared statement placeholders are replaced/logged with their assigned
@@ -75,7 +74,7 @@ impl Connection {
let trace_fn: fn(&str) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
let s = String::from_utf8_lossy(c_slice);
- let _ = catch_unwind(|| trace_fn(&s));
+ drop(catch_unwind(|| trace_fn(&s)));
}
let c = self.db.borrow_mut();
@@ -89,7 +88,7 @@ impl Connection {
}
}
- /// `feature = "trace"` Register or clear a callback function that can be
+ /// Register or clear a callback function that can be
/// used for profiling the execution of SQL statements.
///
/// There can only be a single profiler defined for each database
@@ -109,7 +108,7 @@ impl Connection {
nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32,
);
- let _ = catch_unwind(|| profile_fn(&s, duration));
+ drop(catch_unwind(|| profile_fn(&s, duration)));
}
let c = self.db.borrow_mut();
diff --git a/src/transaction.rs b/src/transaction.rs
index a1b287d..296b2aa 100644
--- a/src/transaction.rs
+++ b/src/transaction.rs
@@ -169,6 +169,7 @@ impl Transaction<'_> {
/// Get the current setting for what happens to the transaction when it is
/// dropped.
#[inline]
+ #[must_use]
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
@@ -177,7 +178,7 @@ impl Transaction<'_> {
/// dropped.
#[inline]
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
- self.drop_behavior = drop_behavior
+ self.drop_behavior = drop_behavior;
}
/// A convenience method which consumes and commits a transaction.
@@ -296,6 +297,7 @@ impl Savepoint<'_> {
/// Get the current setting for what happens to the savepoint when it is
/// dropped.
#[inline]
+ #[must_use]
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
@@ -304,7 +306,7 @@ impl Savepoint<'_> {
/// dropped.
#[inline]
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
- self.drop_behavior = drop_behavior
+ self.drop_behavior = drop_behavior;
}
/// A convenience method which consumes and commits a savepoint.
@@ -378,8 +380,8 @@ impl Connection {
///
/// The transaction defaults to rolling back when it is dropped. If you
/// want the transaction to commit, you must call
- /// [`commit`](Transaction::commit) or [`set_drop_behavior(DropBehavior:
- /// :Commit)`](Transaction::set_drop_behavior).
+ /// [`commit`](Transaction::commit) or
+ /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
///
/// ## Example
///
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
index 38276da..6bfc2f4 100644
--- a/src/types/chrono.rs
+++ b/src/types/chrono.rs
@@ -1,6 +1,6 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
-use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
@@ -83,9 +83,19 @@ impl FromSql for NaiveDateTime {
}
}
-/// Date and time with time zone => UTC RFC3339 timestamp
+/// UTC time => UTC RFC3339 timestamp
/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
-impl<Tz: TimeZone> ToSql for DateTime<Tz> {
+impl ToSql for DateTime<Utc> {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// Local time => UTC RFC3339 timestamp
+/// ("YYYY-MM-DD HH:MM:SS.SSS+00:00").
+impl ToSql for DateTime<Local> {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let date_str = self.with_timezone(&Utc).format("%F %T%.f%:z").to_string();
@@ -93,6 +103,16 @@ impl<Tz: TimeZone> ToSql for DateTime<Tz> {
}
}
+/// Date and time with time zone => RFC3339 timestamp
+/// ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM").
+impl ToSql for DateTime<FixedOffset> {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%F %T%.f%:z").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
impl FromSql for DateTime<Utc> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
@@ -125,13 +145,26 @@ impl FromSql for DateTime<Local> {
}
}
+/// RFC3339 ("YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM") into `DateTime<FixedOffset>`.
+impl FromSql for DateTime<FixedOffset> {
+ #[inline]
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let s = String::column_result(value)?;
+ Self::parse_from_rfc3339(s.as_str())
+ .or_else(|_| Self::parse_from_str(s.as_str(), "%F %T%.f%:z"))
+ .map_err(|e| FromSqlError::Other(Box::new(e)))
+ }
+}
+
#[cfg(test)]
mod test {
use crate::{
types::{FromSql, ValueRef},
Connection, Result,
};
- use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+ use chrono::{
+ DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
+ };
fn checked_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
@@ -234,6 +267,23 @@ mod test {
}
#[test]
+ fn test_date_time_fixed() -> Result<()> {
+ let db = checked_memory_handle()?;
+ let time = DateTime::parse_from_rfc3339("2020-04-07T11:23:45+04:00").unwrap();
+
+ db.execute("INSERT INTO foo (t) VALUES (?)", [time])?;
+
+ // Stored string should preserve timezone offset
+ let s: String = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert!(s.ends_with("+04:00"));
+
+ let v: DateTime<FixedOffset> = db.query_row("SELECT t FROM foo", [], |r| r.get(0))?;
+ assert_eq!(time.offset(), v.offset());
+ assert_eq!(time, v);
+ Ok(())
+ }
+
+ #[test]
fn test_sqlite_functions() -> Result<()> {
let db = checked_memory_handle()?;
let result: Result<NaiveTime> = db.query_row("SELECT CURRENT_TIME", [], |r| r.get(0));
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index 7381fdf..88bdd14 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -15,16 +15,14 @@ pub enum FromSqlError {
/// requested type.
OutOfRange(i64),
- /// `feature = "i128_blob"` Error returned when reading an `i128` from a
- /// blob with a size other than 16. Only available when the `i128_blob`
- /// feature is enabled.
- #[cfg(feature = "i128_blob")]
- InvalidI128Size(usize),
-
- /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
- /// a size other than 16. Only available when the `uuid` feature is enabled.
- #[cfg(feature = "uuid")]
- InvalidUuidSize(usize),
+ /// Error when the blob result returned by SQLite cannot be stored into the
+ /// requested type due to a size mismatch.
+ InvalidBlobSize {
+ /// The expected size of the blob.
+ expected_size: usize,
+ /// The actual size of the blob that was returned.
+ blob_size: usize,
+ },
/// An error case available for implementors of the [`FromSql`] trait.
Other(Box<dyn Error + Send + Sync + 'static>),
@@ -35,10 +33,16 @@ impl PartialEq for FromSqlError {
match (self, other) {
(FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
- #[cfg(feature = "i128_blob")]
- (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
- #[cfg(feature = "uuid")]
- (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
+ (
+ FromSqlError::InvalidBlobSize {
+ expected_size: es1,
+ blob_size: bs1,
+ },
+ FromSqlError::InvalidBlobSize {
+ expected_size: es2,
+ blob_size: bs2,
+ },
+ ) => es1 == es2 && bs1 == bs2,
(..) => false,
}
}
@@ -49,13 +53,15 @@ impl fmt::Display for FromSqlError {
match *self {
FromSqlError::InvalidType => write!(f, "Invalid type"),
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(s) => {
- write!(f, "Cannot read 128bit value out of {} byte blob", s)
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(s) => {
- write!(f, "Cannot read UUID value out of {} byte blob", s)
+ FromSqlError::InvalidBlobSize {
+ expected_size,
+ blob_size,
+ } => {
+ write!(
+ f,
+ "Cannot read {} byte value out of {} byte blob",
+ expected_size, blob_size
+ )
}
FromSqlError::Other(ref err) => err.fmt(f),
}
@@ -171,37 +177,38 @@ impl FromSql for std::sync::Arc<str> {
impl FromSql for Vec<u8> {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value.as_blob().map(|b| b.to_vec())
+ value.as_blob().map(<[u8]>::to_vec)
+ }
+}
+
+impl<const N: usize> FromSql for [u8; N] {
+ #[inline]
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let slice = value.as_blob()?;
+ slice.try_into().map_err(|_| FromSqlError::InvalidBlobSize {
+ expected_size: N,
+ blob_size: slice.len(),
+ })
}
}
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
impl FromSql for i128 {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- use byteorder::{BigEndian, ByteOrder};
-
- value.as_blob().and_then(|bytes| {
- if bytes.len() == 16 {
- Ok(BigEndian::read_i128(bytes) ^ (1i128 << 127))
- } else {
- Err(FromSqlError::InvalidI128Size(bytes.len()))
- }
- })
+ let bytes = <[u8; 16]>::column_result(value)?;
+ Ok(i128::from_be_bytes(bytes) ^ (1_i128 << 127))
}
}
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
impl FromSql for uuid::Uuid {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value
- .as_blob()
- .and_then(|bytes| {
- uuid::Builder::from_slice(bytes)
- .map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
- })
- .map(|mut builder| builder.build())
+ let bytes = <[u8; 16]>::column_result(value)?;
+ Ok(uuid::Uuid::from_u128(u128::from_be_bytes(bytes)))
}
}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 706be0c..4e524b2 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -41,22 +41,24 @@ For example, to store datetimes as `i64`s counting the number of seconds since
the Unix epoch:
```
-use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use rusqlite::Result;
pub struct DateTimeSql(pub time::OffsetDateTime);
impl FromSql for DateTimeSql {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
- i64::column_result(value).map(|as_i64| {
- DateTimeSql(time::OffsetDateTime::from_unix_timestamp(as_i64))
+ i64::column_result(value).and_then(|as_i64| {
+ time::OffsetDateTime::from_unix_timestamp(as_i64)
+ .map(|odt| DateTimeSql(odt))
+ .map_err(|err| FromSqlError::Other(Box::new(err)))
})
}
}
impl ToSql for DateTimeSql {
fn to_sql(&self) -> Result<ToSqlOutput> {
- Ok(self.0.timestamp().into())
+ Ok(self.0.unix_timestamp().into())
}
}
```
@@ -75,14 +77,18 @@ pub use self::value_ref::ValueRef;
use std::fmt;
#[cfg(feature = "chrono")]
+#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
mod chrono;
mod from_sql;
#[cfg(feature = "serde_json")]
+#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
mod serde_json;
#[cfg(feature = "time")]
+#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
mod time;
mod to_sql;
#[cfg(feature = "url")]
+#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
mod url;
mod value;
mod value_ref;
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
index f018032..a9761bd 100644
--- a/src/types/serde_json.rs
+++ b/src/types/serde_json.rs
@@ -17,12 +17,8 @@ impl ToSql for Value {
impl FromSql for Value {
#[inline]
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- match value {
- ValueRef::Text(s) => serde_json::from_slice(s),
- ValueRef::Blob(b) => serde_json::from_slice(b),
- _ => return Err(FromSqlError::InvalidType),
- }
- .map_err(|err| FromSqlError::Other(Box::new(err)))
+ let bytes = value.as_bytes()?;
+ serde_json::from_slice(bytes).map_err(|err| FromSqlError::Other(Box::new(err)))
}
}
diff --git a/src/types/time.rs b/src/types/time.rs
index ac182a9..4e2811e 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,16 +1,35 @@
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
-use crate::Result;
+use crate::{Error, Result};
+use time::format_description::well_known::Rfc3339;
+use time::format_description::FormatItem;
+use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
-const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
-const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S.%NZ";
-const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
+const PRIMITIVE_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
+const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
+const PRIMITIVE_DATE_TIME_Z_FORMAT: &[FormatItem<'_>] =
+ format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]Z");
+const OFFSET_SHORT_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
+);
+const OFFSET_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour sign:mandatory]:[offset_minute]"
+);
+const LEGACY_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!(
+ "[year]-[month]-[day] [hour]:[minute]:[second]:[subsecond] [offset_hour sign:mandatory]:[offset_minute]"
+);
impl ToSql for OffsetDateTime {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
+ // FIXME keep original offset
+ let time_string = self
+ .to_offset(UtcOffset::UTC)
+ .format(&PRIMITIVE_DATE_TIME_Z_FORMAT)
+ .map_err(|err| Error::ToSqlConversionFailure(err.into()))?;
Ok(ToSqlOutput::from(time_string))
}
}
@@ -18,13 +37,29 @@ impl ToSql for OffsetDateTime {
impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str().and_then(|s| {
+ if s.len() > 10 && s.as_bytes()[10] == b'T' {
+ // YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
+ return OffsetDateTime::parse(s, &Rfc3339)
+ .map_err(|err| FromSqlError::Other(Box::new(err)));
+ }
+ let s = s.strip_suffix('Z').unwrap_or(s);
match s.len() {
- 19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
- _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
- .map(|d| d.assume_utc())
+ len if len <= 19 => {
+ // TODO YYYY-MM-DDTHH:MM:SS
+ PrimitiveDateTime::parse(s, &PRIMITIVE_SHORT_DATE_TIME_FORMAT)
+ .map(PrimitiveDateTime::assume_utc)
+ }
+ _ if s.as_bytes()[19] == b':' => {
+ // legacy
+ OffsetDateTime::parse(s, &LEGACY_DATE_TIME_FORMAT)
+ }
+ _ if s.as_bytes()[19] == b'.' => OffsetDateTime::parse(s, &OFFSET_DATE_TIME_FORMAT)
.or_else(|err| {
- OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err)
+ PrimitiveDateTime::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)
+ .map(PrimitiveDateTime::assume_utc)
+ .map_err(|_| err)
}),
+ _ => OffsetDateTime::parse(s, &OFFSET_SHORT_DATE_TIME_FORMAT),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
})
@@ -34,23 +69,19 @@ impl FromSql for OffsetDateTime {
#[cfg(test)]
mod test {
use crate::{Connection, Result};
- use std::time::Duration;
+ use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
- fn checked_memory_handle() -> Result<Connection> {
- let db = Connection::open_in_memory()?;
- db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
- Ok(db)
- }
-
#[test]
fn test_offset_date_time() -> Result<()> {
- let db = checked_memory_handle()?;
+ let db = Connection::open_in_memory()?;
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")?;
let mut ts_vec = vec![];
- let make_datetime =
- |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos);
+ let make_datetime = |secs: i128, nanos: i128| {
+ OffsetDateTime::from_unix_timestamp_nanos(1_000_000_000 * secs + nanos).unwrap()
+ };
ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
@@ -72,8 +103,55 @@ mod test {
}
#[test]
+ fn test_string_values() -> Result<()> {
+ let db = Connection::open_in_memory()?;
+ for (s, t) in vec![
+ (
+ "2013-10-07 08:23:19",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T08:23:19Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19.120",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 08:23:19.120Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T08:23:19.120Z",
+ Ok(OffsetDateTime::parse("2013-10-07T08:23:19.120Z", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 04:23:19-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19-04:00", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07 04:23:19.120-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
+ ),
+ (
+ "2013-10-07T04:23:19.120-04:00",
+ Ok(OffsetDateTime::parse("2013-10-07T04:23:19.120-04:00", &Rfc3339).unwrap()),
+ ),
+ ] {
+ let result: Result<OffsetDateTime> = db.query_row("SELECT ?", [s], |r| r.get(0));
+ assert_eq!(result, t);
+ }
+ Ok(())
+ }
+
+ #[test]
fn test_sqlite_functions() -> Result<()> {
- let db = checked_memory_handle()?;
+ let db = Connection::open_in_memory()?;
let result: Result<OffsetDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", [], |r| r.get(0));
assert!(result.is_ok());
@@ -82,7 +160,7 @@ mod test {
#[test]
fn test_param() -> Result<()> {
- let db = checked_memory_handle()?;
+ let db = Connection::open_in_memory()?;
let result: Result<bool> = db.query_row("SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute')", [OffsetDateTime::now_utc()], |r| r.get(0));
assert!(result.is_ok());
Ok(())
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index 1bf7711..2445339 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -16,13 +16,15 @@ pub enum ToSqlOutput<'a> {
/// An owned SQLite-representable value.
Owned(Value),
- /// `feature = "blob"` A BLOB of the given length that is filled with
+ /// A BLOB of the given length that is filled with
/// zeroes.
#[cfg(feature = "blob")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "blob")))]
ZeroBlob(i32),
/// `feature = "array"`
#[cfg(feature = "array")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "array")))]
Array(Array),
}
@@ -70,9 +72,11 @@ from_value!(Vec<u8>);
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
// worth adding another case to Value.
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
from_value!(i128);
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
from_value!(uuid::Uuid);
impl ToSql for ToSqlOutput<'_> {
@@ -162,9 +166,11 @@ to_sql_self!(f32);
to_sql_self!(f64);
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
to_sql_self!(i128);
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
to_sql_self!(uuid::Uuid);
macro_rules! to_sql_self_fallible(
@@ -218,6 +224,13 @@ impl ToSql for Vec<u8> {
}
}
+impl<const N: usize> ToSql for [u8; N] {
+ #[inline]
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(&self[..]))
+ }
+}
+
impl ToSql for [u8] {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
@@ -260,6 +273,15 @@ mod test {
}
#[test]
+ fn test_u8_array() {
+ let a: [u8; 99] = [0u8; 99];
+ let _a: &[&dyn ToSql] = crate::params![a];
+ let r = ToSql::to_sql(&a);
+
+ assert!(r.is_ok());
+ }
+
+ #[test]
fn test_cow_str() {
use std::borrow::Cow;
let s = "str";
diff --git a/src/types/value.rs b/src/types/value.rs
index 944655c..ca3ee9f 100644
--- a/src/types/value.rs
+++ b/src/types/value.rs
@@ -41,19 +41,18 @@ impl From<isize> for Value {
}
#[cfg(feature = "i128_blob")]
+#[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))]
impl From<i128> for Value {
#[inline]
fn from(i: i128) -> Value {
- use byteorder::{BigEndian, ByteOrder};
- let mut buf = vec![0u8; 16];
// We store these biased (e.g. with the most significant bit flipped)
// so that comparisons with negative numbers work properly.
- BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
- Value::Blob(buf)
+ Value::Blob(i128::to_be_bytes(i ^ (1_i128 << 127)).to_vec())
}
}
#[cfg(feature = "uuid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
impl From<uuid::Uuid> for Value {
#[inline]
fn from(id: uuid::Uuid) -> Value {
@@ -130,6 +129,7 @@ where
impl Value {
/// Returns SQLite fundamental datatype.
#[inline]
+ #[must_use]
pub fn data_type(&self) -> Type {
match *self {
Value::Null => Type::Null,
diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs
index 446ad08..c0d81ca 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -22,6 +22,7 @@ pub enum ValueRef<'a> {
impl ValueRef<'_> {
/// Returns SQLite fundamental datatype.
#[inline]
+ #[must_use]
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,
@@ -45,6 +46,19 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Integer`, returns the integral value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Integer(i) => Ok(Some(i)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// If `self` is case `Real`, returns the floating point value. Otherwise,
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::
/// InvalidColumnType).
@@ -56,6 +70,19 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Real`, returns the floating point value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Real(f) => Ok(Some(f)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// If `self` is case `Text`, returns the string value. Otherwise, returns
/// [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
#[inline]
@@ -68,6 +95,21 @@ impl<'a> ValueRef<'a> {
}
}
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Text`, returns the string value.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Text(t) => std::str::from_utf8(t)
+ .map_err(|e| FromSqlError::Other(Box::new(e)))
+ .map(Some),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
/// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
/// [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
#[inline]
@@ -77,6 +119,41 @@ impl<'a> ValueRef<'a> {
_ => Err(FromSqlError::InvalidType),
}
}
+
+ /// If `self` is case `Null` returns None.
+ /// If `self` is case `Blob`, returns the byte slice.
+ /// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
+ /// InvalidColumnType).
+ #[inline]
+ pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Blob(b) => Ok(Some(b)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// Returns the byte slice that makes up this ValueRef if it's either
+ /// [`ValueRef::Blob`] or [`ValueRef::Text`].
+ #[inline]
+ pub fn as_bytes(&self) -> FromSqlResult<&'a [u8]> {
+ match self {
+ ValueRef::Text(s) | ValueRef::Blob(s) => Ok(s),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// If `self` is case `Null` returns None.
+ /// If `self` is [`ValueRef::Blob`] or [`ValueRef::Text`] returns the byte
+ /// slice that makes up this value
+ #[inline]
+ pub fn as_bytes_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
+ match *self {
+ ValueRef::Null => Ok(None),
+ ValueRef::Text(s) | ValueRef::Blob(s) => Ok(Some(s)),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
}
impl From<ValueRef<'_>> for Value {
@@ -152,7 +229,7 @@ impl<'a> ValueRef<'a> {
!text.is_null(),
"unexpected SQLITE_TEXT value type with NULL data"
);
- let s = from_raw_parts(text as *const u8, len as usize);
+ let s = from_raw_parts(text.cast::<u8>(), len as usize);
ValueRef::Text(s)
}
ffi::SQLITE_BLOB => {
@@ -170,7 +247,7 @@ impl<'a> ValueRef<'a> {
!blob.is_null(),
"unexpected SQLITE_BLOB value type with NULL data"
);
- ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
+ ValueRef::Blob(from_raw_parts(blob.cast::<u8>(), len as usize))
} else {
// The return value from sqlite3_value_blob() for a zero-length BLOB
// is a NULL pointer.
diff --git a/src/unlock_notify.rs b/src/unlock_notify.rs
index 5f9cdbf..8fba6b3 100644
--- a/src/unlock_notify.rs
+++ b/src/unlock_notify.rs
@@ -1,22 +1,17 @@
//! [Unlock Notification](http://sqlite.org/unlock_notify.html)
use std::os::raw::c_int;
-#[cfg(feature = "unlock_notify")]
use std::os::raw::c_void;
-#[cfg(feature = "unlock_notify")]
use std::panic::catch_unwind;
-#[cfg(feature = "unlock_notify")]
use std::sync::{Condvar, Mutex};
use crate::ffi;
-#[cfg(feature = "unlock_notify")]
struct UnlockNotification {
cond: Condvar, // Condition variable to wait on
mutex: Mutex<bool>, // Mutex to protect structure
}
-#[cfg(feature = "unlock_notify")]
#[allow(clippy::mutex_atomic)]
impl UnlockNotification {
fn new() -> UnlockNotification {
@@ -27,30 +22,33 @@ impl UnlockNotification {
}
fn fired(&self) {
- let mut flag = self.mutex.lock().unwrap();
+ let mut flag = unpoison(self.mutex.lock());
*flag = true;
self.cond.notify_one();
}
fn wait(&self) {
- let mut fired = self.mutex.lock().unwrap();
+ let mut fired = unpoison(self.mutex.lock());
while !*fired {
- fired = self.cond.wait(fired).unwrap();
+ fired = unpoison(self.cond.wait(fired));
}
}
}
+#[inline]
+fn unpoison<T>(r: Result<T, std::sync::PoisonError<T>>) -> T {
+ r.unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
/// This function is an unlock-notify callback
-#[cfg(feature = "unlock_notify")]
unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) {
use std::slice::from_raw_parts;
let args = from_raw_parts(ap_arg as *const &UnlockNotification, n_arg as usize);
for un in args {
- let _ = catch_unwind(std::panic::AssertUnwindSafe(|| un.fired()));
+ drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired())));
}
}
-#[cfg(feature = "unlock_notify")]
pub unsafe fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool {
rc == ffi::SQLITE_LOCKED_SHAREDCACHE
|| (rc & 0xFF) == ffi::SQLITE_LOCKED
@@ -87,17 +85,6 @@ pub unsafe fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int {
rc
}
-#[cfg(not(feature = "unlock_notify"))]
-pub unsafe fn is_locked(_db: *mut ffi::sqlite3, _rc: c_int) -> bool {
- unreachable!()
-}
-
-#[cfg(not(feature = "unlock_notify"))]
-pub unsafe fn wait_for_unlock_notify(_db: *mut ffi::sqlite3) -> c_int {
- unreachable!()
-}
-
-#[cfg(feature = "unlock_notify")]
#[cfg(test)]
mod test {
use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior};
diff --git a/src/util/small_cstr.rs b/src/util/small_cstr.rs
index 7349df0..4543c62 100644
--- a/src/util/small_cstr.rs
+++ b/src/util/small_cstr.rs
@@ -1,7 +1,7 @@
use smallvec::{smallvec, SmallVec};
use std::ffi::{CStr, CString, NulError};
-/// Similar to std::ffi::CString, but avoids heap allocating if the string is
+/// Similar to `std::ffi::CString`, but avoids heap allocating if the string is
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -10,7 +10,7 @@ pub(crate) struct SmallCString(smallvec::SmallVec<[u8; 16]>);
impl SmallCString {
#[inline]
pub fn new(s: &str) -> Result<Self, NulError> {
- if s.as_bytes().contains(&0u8) {
+ if s.as_bytes().contains(&0_u8) {
return Err(Self::fabricate_nul_error(s));
}
let mut buf = SmallVec::with_capacity(s.len() + 1);
@@ -31,7 +31,7 @@ impl SmallCString {
/// Get the bytes not including the NUL terminator. E.g. the bytes which
/// make up our `str`:
/// - `SmallCString::new("foo").as_bytes_without_nul() == b"foo"`
- /// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"
+ /// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"`
#[inline]
pub fn as_bytes_without_nul(&self) -> &[u8] {
self.debug_checks();
diff --git a/src/util/sqlite_string.rs b/src/util/sqlite_string.rs
index d131b56..da261ba 100644
--- a/src/util/sqlite_string.rs
+++ b/src/util/sqlite_string.rs
@@ -38,7 +38,7 @@ pub(crate) struct SqliteMallocString {
impl SqliteMallocString {
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
- /// allocated by sqlite3_malloc, and that SQLite expects us to free it!
+ /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
#[inline]
pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
Self {
@@ -48,7 +48,7 @@ impl SqliteMallocString {
}
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
- /// allocated by sqlite3_malloc, and that SQLite expects us to free it!
+ /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
#[inline]
pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
@@ -95,13 +95,13 @@ impl SqliteMallocString {
/// If `s` contains internal NULs, we'll replace them with
/// `NUL_REPLACE_CHAR`.
///
- /// Except for debug_asserts which may trigger during testing, this function
- /// never panics. If we hit integer overflow or the allocation fails, we
- /// call `handle_alloc_error` which aborts the program after calling a
- /// global hook.
+ /// Except for `debug_assert`s which may trigger during testing, this
+ /// function never panics. If we hit integer overflow or the allocation
+ /// fails, we call `handle_alloc_error` which aborts the program after
+ /// calling a global hook.
///
/// This means it's safe to use in extern "C" functions even outside of
- /// catch_unwind.
+ /// `catch_unwind`.
pub(crate) fn from_str(s: &str) -> Self {
use std::convert::TryFrom;
let s = if s.as_bytes().contains(&0) {
@@ -120,7 +120,7 @@ impl SqliteMallocString {
// `>` because we added 1.
debug_assert!(len_to_alloc > 0);
debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
- NonNull::new(ffi::sqlite3_malloc(len_to_alloc) as *mut c_char)
+ NonNull::new(ffi::sqlite3_malloc(len_to_alloc).cast::<c_char>())
})
.unwrap_or_else(|| {
use std::alloc::{handle_alloc_error, Layout};
@@ -138,7 +138,7 @@ impl SqliteMallocString {
// Note: This call does not return.
handle_alloc_error(layout);
});
- let buf: *mut c_char = res_ptr.as_ptr() as *mut c_char;
+ let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
src_ptr.copy_to_nonoverlapping(buf, src_len);
buf.add(src_len).write(0);
debug_assert_eq!(std::ffi::CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
diff --git a/src/version.rs b/src/version.rs
index 6f56ee2..d70af7e 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -6,6 +6,7 @@ use std::ffi::CStr;
///
/// See [`sqlite3_libversion_number()`](https://www.sqlite.org/c3ref/libversion.html).
#[inline]
+#[must_use]
pub fn version_number() -> i32 {
unsafe { ffi::sqlite3_libversion_number() }
}
@@ -14,6 +15,7 @@ pub fn version_number() -> i32 {
///
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
#[inline]
+#[must_use]
pub fn version() -> &'static str {
let cstr = unsafe { CStr::from_ptr(ffi::sqlite3_libversion()) };
cstr.to_str()
diff --git a/src/vtab/array.rs b/src/vtab/array.rs
index 713604c..adfd9c9 100644
--- a/src/vtab/array.rs
+++ b/src/vtab/array.rs
@@ -1,4 +1,4 @@
-//! `feature = "array"` Array Virtual Table.
+//! Array Virtual Table.
//!
//! Note: `rarray`, not `carray` is the name of the table valued function we
//! define.
@@ -41,10 +41,10 @@ use crate::{Connection, Result};
// http://sqlite.org/bindptr.html
-pub(crate) const ARRAY_TYPE: *const c_char = b"rarray\0" as *const u8 as *const c_char;
+pub(crate) const ARRAY_TYPE: *const c_char = (b"rarray\0" as *const u8).cast::<c_char>();
pub(crate) unsafe extern "C" fn free_array(p: *mut c_void) {
- let _: Array = Rc::from_raw(p as *const Vec<Value>);
+ drop(Rc::from_raw(p as *const Vec<Value>));
}
/// Array parameter / pointer
@@ -57,7 +57,7 @@ impl ToSql for Array {
}
}
-/// `feature = "array"` Register the "rarray" module.
+/// Register the "rarray" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("rarray", eponymous_only_module::<ArrayTab>(), aux)
@@ -91,8 +91,8 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
// Index of the pointer= constraint
- let mut ptr_idx = None;
- for (i, constraint) in info.constraints().enumerate() {
+ let mut ptr_idx = false;
+ for (constraint, mut constraint_usage) in info.constraints_and_usages() {
if !constraint.is_usable() {
continue;
}
@@ -100,20 +100,17 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
continue;
}
if let CARRAY_COLUMN_POINTER = constraint.column() {
- ptr_idx = Some(i);
- }
- }
- if let Some(ptr_idx) = ptr_idx {
- {
- let mut constraint_usage = info.constraint_usage(ptr_idx);
+ ptr_idx = true;
constraint_usage.set_argv_index(1);
constraint_usage.set_omit(true);
}
- info.set_estimated_cost(1f64);
+ }
+ if ptr_idx {
+ info.set_estimated_cost(1_f64);
info.set_estimated_rows(100);
info.set_idx_num(1);
} else {
- info.set_estimated_cost(2_147_483_647f64);
+ info.set_estimated_cost(2_147_483_647_f64);
info.set_estimated_rows(2_147_483_647);
info.set_idx_num(0);
}
diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs
index 096f272..df3529a 100644
--- a/src/vtab/csvtab.rs
+++ b/src/vtab/csvtab.rs
@@ -1,4 +1,4 @@
-//! `feature = "csvtab"` CSV Virtual Table.
+//! CSV Virtual Table.
//!
//! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C
//! extension: `https://www.sqlite.org/csv.html`
@@ -35,7 +35,7 @@ use crate::vtab::{
};
use crate::{Connection, Error, Result};
-/// `feature = "csvtab"` Register the "csv" module.
+/// Register the "csv" module.
/// ```sql
/// CREATE VIRTUAL TABLE vtab USING csv(
/// filename=FILENAME -- Name of file containing CSV content
@@ -212,7 +212,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
if n_col.is_none() && schema.is_none() {
cols = headers
.into_iter()
- .map(|header| escape_double_quote(&header).into_owned())
+ .map(|header| escape_double_quote(header).into_owned())
.collect();
}
}
diff --git a/src/vtab/mod.rs b/src/vtab/mod.rs
index f364f1f..bdb6509 100644
--- a/src/vtab/mod.rs
+++ b/src/vtab/mod.rs
@@ -1,4 +1,4 @@
-//! `feature = "vtab"` Create virtual tables.
+//! Create virtual tables.
//!
//! Follow these steps to create your own virtual table:
//! 1. Write implementation of [`VTab`] and [`VTabCursor`] traits.
@@ -57,7 +57,7 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
// ffi::sqlite3_vtab => VTab
// ffi::sqlite3_vtab_cursor => VTabCursor
-/// `feature = "vtab"` Virtual table module
+/// Virtual table module
///
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
#[repr(transparent)]
@@ -79,17 +79,19 @@ union ModuleZeroHack {
// structs are allowed to be zeroed.
const ZERO_MODULE: ffi::sqlite3_module = unsafe {
ModuleZeroHack {
- bytes: [0u8; std::mem::size_of::<ffi::sqlite3_module>()],
+ bytes: [0_u8; std::mem::size_of::<ffi::sqlite3_module>()],
}
.module
};
-/// `feature = "vtab"` Create a read-only virtual table implementation.
+/// 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.
+ #[allow(clippy::needless_update)]
&Module {
base: ffi::sqlite3_module {
// We don't use V3
@@ -122,13 +124,15 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
}
}
-/// `feature = "vtab"` Create an eponymous only virtual table implementation.
+/// 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> {
// 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
@@ -187,18 +191,18 @@ impl VTabConnection {
}
}
-/// `feature = "vtab"` Virtual table instance trait.
+/// Virtual table instance trait.
///
/// # Safety
///
-/// The first item in a struct implementing VTab must be
+/// 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: ffi::sqlite3_vtab,
+/// base: rusqlite::vtab::sqlite3_vtab,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
@@ -228,7 +232,7 @@ pub unsafe trait VTab<'vtab>: Sized {
fn open(&'vtab self) -> Result<Self::Cursor>;
}
-/// `feature = "vtab"` Non-eponymous virtual table instance trait.
+/// Non-eponymous virtual table instance trait.
///
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
pub trait CreateVTab<'vtab>: VTab<'vtab> {
@@ -257,7 +261,7 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
}
}
-/// `feature = "vtab"` Index constraint operator.
+/// Index constraint operator.
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
#[derive(Debug, PartialEq)]
#[allow(non_snake_case, non_camel_case_types, missing_docs)]
@@ -277,6 +281,8 @@ pub enum IndexConstraintOp {
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
}
@@ -297,20 +303,36 @@ impl From<u8> for IndexConstraintOp {
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),
}
}
}
-/// `feature = "vtab"` Pass information into and receive the reply from the
+/// Pass information into and receive the reply from the
/// [`VTab::best_index`] method.
///
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
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) };
@@ -321,6 +343,7 @@ impl IndexInfo {
/// 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) };
@@ -331,6 +354,7 @@ impl IndexInfo {
/// 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 }
}
@@ -370,6 +394,7 @@ impl IndexInfo {
/// 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 {
@@ -383,6 +408,30 @@ impl IndexInfo {
// TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.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>,
@@ -393,7 +442,7 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
#[inline]
fn next(&mut self) -> Option<IndexConstraint<'a>> {
- self.iter.next().map(|raw| IndexConstraint(raw))
+ self.iter.next().map(IndexConstraint)
}
#[inline]
@@ -402,30 +451,33 @@ impl<'a> Iterator for IndexConstraintIter<'a> {
}
}
-/// `feature = "vtab"` WHERE clause constraint.
+/// 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
}
}
-/// `feature = "vtab"` Information about what parameters to pass to
+/// Information about what parameters to pass to
/// [`VTabCursor::filter`].
pub struct IndexConstraintUsage<'a>(&'a mut ffi::sqlite3_index_constraint_usage);
@@ -454,7 +506,7 @@ impl<'a> Iterator for OrderByIter<'a> {
#[inline]
fn next(&mut self) -> Option<OrderBy<'a>> {
- self.iter.next().map(|raw| OrderBy(raw))
+ self.iter.next().map(OrderBy)
}
#[inline]
@@ -463,31 +515,35 @@ impl<'a> Iterator for OrderByIter<'a> {
}
}
-/// `feature = "vtab"` A column of the ORDER BY clause.
+/// 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
}
}
-/// `feature = "vtab"` Virtual table cursor trait.
+/// Virtual table cursor trait.
+///
+/// # Safety
///
/// Implementations must be like:
/// ```rust,ignore
/// #[repr(C)]
/// struct MyTabCursor {
/// /// Base class. Must be first
-/// base: ffi::sqlite3_vtab_cursor,
+/// base: rusqlite::vtab::sqlite3_vtab_cursor,
/// /* Virtual table implementations will typically add additional fields */
/// }
/// ```
@@ -514,7 +570,7 @@ pub unsafe trait VTabCursor: Sized {
fn rowid(&self) -> Result<i64>;
}
-/// `feature = "vtab"` Context is used by [`VTabCursor::column`] to specify the
+/// Context is used by [`VTabCursor::column`] to specify the
/// cell value.
pub struct Context(*mut ffi::sqlite3_context);
@@ -530,7 +586,7 @@ impl Context {
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
}
-/// `feature = "vtab"` Wrapper to [`VTabCursor::filter`] arguments, the values
+/// Wrapper to [`VTabCursor::filter`] arguments, the values
/// requested by [`VTab::best_index`].
pub struct Values<'a> {
args: &'a [*mut ffi::sqlite3_value],
@@ -539,12 +595,14 @@ pub struct Values<'a> {
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()
}
@@ -558,21 +616,17 @@ impl Values<'_> {
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx, value.data_type(), err)
}
- FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
- #[cfg(feature = "i128_blob")]
- FromSqlError::InvalidI128Size(_) => {
- Error::InvalidColumnType(idx, idx.to_string(), value.data_type())
- }
- #[cfg(feature = "uuid")]
- FromSqlError::InvalidUuidSize(_) => {
+ 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];
@@ -591,6 +645,7 @@ impl Values<'_> {
/// Turns `Values` into an iterator.
#[inline]
+ #[must_use]
pub fn iter(&self) -> ValueIter<'_> {
ValueIter {
iter: self.args.iter(),
@@ -630,7 +685,7 @@ impl<'a> Iterator for ValueIter<'a> {
}
impl Connection {
- /// `feature = "vtab"` Register a virtual table implementation.
+ /// Register a virtual table implementation.
///
/// Step 3 of [Creating New Virtual Table
/// Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
@@ -661,7 +716,7 @@ impl InnerConnection {
self.db(),
c_name.as_ptr(),
&module.base,
- boxed_aux as *mut c_void,
+ boxed_aux.cast::<c_void>(),
Some(free_boxed_value::<T::Aux>),
)
}
@@ -680,17 +735,19 @@ impl InnerConnection {
}
}
-/// `feature = "vtab"` Escape double-quote (`"`) character occurrences by
+/// 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("\"", "\"\""))
+ Owned(identifier.replace('"', "\"\""))
} else {
Borrowed(identifier)
}
}
-/// `feature = "vtab"` Dequote string
+/// Dequote string
+#[must_use]
pub fn dequote(s: &str) -> &str {
if s.len() < 2 {
return s;
@@ -703,11 +760,12 @@ pub fn dequote(s: &str) -> &str {
_ => s,
}
}
-/// `feature = "vtab"` The boolean can be one of:
+/// 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")
@@ -728,7 +786,7 @@ pub fn parse_boolean(s: &str) -> Option<bool> {
// FIXME copy/paste from function.rs
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
- let _: Box<T> = Box::from_raw(p as *mut T);
+ drop(Box::from_raw(p.cast::<T>()));
}
unsafe extern "C" fn rust_create<'vtab, T>(
@@ -745,7 +803,7 @@ where
use std::ffi::CStr;
let mut conn = VTabConnection(db);
- let aux = aux as *mut T::Aux;
+ let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
@@ -757,7 +815,7 @@ where
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 as *mut ffi::sqlite3_vtab;
+ *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
@@ -797,7 +855,7 @@ where
use std::ffi::CStr;
let mut conn = VTabConnection(db);
- let aux = aux as *mut T::Aux;
+ let aux = aux.cast::<T::Aux>();
let args = slice::from_raw_parts(argv, argc as usize);
let vec = args
.iter()
@@ -809,7 +867,7 @@ where
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 as *mut ffi::sqlite3_vtab;
+ *pp_vtab = boxed_vtab.cast::<ffi::sqlite3_vtab>();
ffi::SQLITE_OK
} else {
let err = error_from_sqlite_code(rc, None);
@@ -842,7 +900,7 @@ unsafe extern "C" fn rust_best_index<'vtab, T>(
where
T: VTab<'vtab>,
{
- let vt = vtab as *mut T;
+ let vt = vtab.cast::<T>();
let mut idx_info = IndexInfo(info);
match (*vt).best_index(&mut idx_info) {
Ok(_) => ffi::SQLITE_OK,
@@ -866,8 +924,8 @@ where
if vtab.is_null() {
return ffi::SQLITE_OK;
}
- let vtab = vtab as *mut T;
- let _: Box<T> = Box::from_raw(vtab);
+ let vtab = vtab.cast::<T>();
+ drop(Box::from_raw(vtab));
ffi::SQLITE_OK
}
@@ -878,10 +936,10 @@ where
if vtab.is_null() {
return ffi::SQLITE_OK;
}
- let vt = vtab as *mut T;
+ let vt = vtab.cast::<T>();
match (*vt).destroy() {
Ok(_) => {
- let _: Box<T> = Box::from_raw(vt);
+ drop(Box::from_raw(vt));
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
@@ -904,11 +962,11 @@ unsafe extern "C" fn rust_open<'vtab, T: 'vtab>(
where
T: VTab<'vtab>,
{
- let vt = vtab as *mut T;
+ 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 as *mut ffi::sqlite3_vtab_cursor;
+ *pp_cursor = boxed_cursor.cast::<ffi::sqlite3_vtab_cursor>();
ffi::SQLITE_OK
}
Err(Error::SqliteFailure(err, s)) => {
@@ -928,8 +986,8 @@ unsafe extern "C" fn rust_close<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_i
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
- let _: Box<C> = Box::from_raw(cr);
+ let cr = cursor.cast::<C>();
+ drop(Box::from_raw(cr));
ffi::SQLITE_OK
}
@@ -969,7 +1027,7 @@ unsafe extern "C" fn rust_eof<C>(cursor: *mut ffi::sqlite3_vtab_cursor) -> c_int
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
(*cr).eof() as c_int
}
@@ -981,7 +1039,7 @@ unsafe extern "C" fn rust_column<C>(
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
let mut ctxt = Context(ctx);
result_error(ctx, (*cr).column(&mut ctxt, i))
}
@@ -993,7 +1051,7 @@ unsafe extern "C" fn rust_rowid<C>(
where
C: VTabCursor,
{
- let cr = cursor as *mut C;
+ let cr = cursor.cast::<C>();
match (*cr).rowid() {
Ok(rowid) => {
*p_rowid = rowid;
@@ -1027,7 +1085,7 @@ unsafe fn cursor_error<T>(cursor: *mut ffi::sqlite3_vtab_cursor, result: Result<
#[cold]
unsafe fn set_err_msg(vtab: *mut ffi::sqlite3_vtab, err_msg: &str) {
if !(*vtab).zErrMsg.is_null() {
- ffi::sqlite3_free((*vtab).zErrMsg as *mut c_void);
+ ffi::sqlite3_free((*vtab).zErrMsg.cast::<c_void>());
}
(*vtab).zErrMsg = alloc(err_msg);
}
@@ -1072,10 +1130,13 @@ fn alloc(s: &str) -> *mut c_char {
}
#[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)]
diff --git a/src/vtab/series.rs b/src/vtab/series.rs
index 31ef86f..f26212a 100644
--- a/src/vtab/series.rs
+++ b/src/vtab/series.rs
@@ -1,4 +1,4 @@
-//! `feature = "series"` Generate series virtual table.
+//! Generate series virtual table.
//!
//! Port of C [generate series
//! "function"](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/series.c):
@@ -15,7 +15,7 @@ use crate::vtab::{
};
use crate::{Connection, Error, Result};
-/// `feature = "series"` Register the "generate_series" module.
+/// Register the "generate_series" module.
pub fn load_module(conn: &Connection) -> Result<()> {
let aux: Option<()> = None;
conn.create_module("generate_series", eponymous_only_module::<SeriesTab>(), aux)
@@ -38,7 +38,7 @@ bitflags::bitflags! {
const STEP = 4;
// output in descending order
const DESC = 8;
- // output in descending order
+ // output in ascending order
const ASC = 16;
// Both start and stop
const BOTH = QueryPlanFlags::START.bits | QueryPlanFlags::STOP.bits;
@@ -123,12 +123,16 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
let order_by_consumed = {
let mut order_bys = info.order_bys();
if let Some(order_by) = order_bys.next() {
- if order_by.is_order_by_desc() {
- idx_num |= QueryPlanFlags::DESC;
+ if order_by.column() == 0 {
+ if order_by.is_order_by_desc() {
+ idx_num |= QueryPlanFlags::DESC;
+ } else {
+ idx_num |= QueryPlanFlags::ASC;
+ }
+ true
} else {
- idx_num |= QueryPlanFlags::ASC;
+ false
}
- true
} else {
false
}