mirror of
https://github.com/isar/rusqlite.git
synced 2025-12-16 08:22:25 +08:00
Merge remote-tracking branch 'origin/master' into sub_type
This commit is contained in:
@@ -57,7 +57,7 @@ impl Connection {
|
||||
}
|
||||
|
||||
/// Prepared statements LRU cache.
|
||||
// #[derive(Debug)] // FIXME: https://github.com/kyren/hashlink/pull/4
|
||||
#[derive(Debug)]
|
||||
pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
|
||||
|
||||
#[allow(clippy::non_send_fields_in_send_ty)]
|
||||
|
||||
@@ -3,12 +3,16 @@ use std::str;
|
||||
use crate::{Error, Result, Statement};
|
||||
|
||||
/// Information about a column of a SQLite query.
|
||||
#[cfg(feature = "column_decltype")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
|
||||
#[derive(Debug)]
|
||||
pub struct Column<'stmt> {
|
||||
name: &'stmt str,
|
||||
decl_type: Option<&'stmt str>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "column_decltype")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
|
||||
impl Column<'_> {
|
||||
/// Returns the name of the column.
|
||||
#[inline]
|
||||
@@ -90,6 +94,8 @@ impl Statement<'_> {
|
||||
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
|
||||
/// column range for this row.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when column name is not valid UTF-8.
|
||||
#[inline]
|
||||
pub fn column_name(&self, col: usize) -> Result<&str> {
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{Connection, Result};
|
||||
/// Database Connection Configuration Options
|
||||
/// See [Database Connection Configuration Options](https://sqlite.org/c3ref/c_dbconfig_enable_fkey.html) for details.
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
#[non_exhaustive]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
|
||||
25
src/error.rs
25
src/error.rs
@@ -141,6 +141,10 @@ pub enum Error {
|
||||
/// byte offset of the start of invalid token
|
||||
offset: c_int,
|
||||
},
|
||||
/// Loadable extension initialization error
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "loadable_extension")))]
|
||||
InitError(ffi::InitError),
|
||||
}
|
||||
|
||||
impl PartialEq for Error {
|
||||
@@ -200,6 +204,8 @@ impl PartialEq for Error {
|
||||
offset: o2,
|
||||
},
|
||||
) => e1 == e2 && m1 == m2 && s1 == s2 && o1 == o2,
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
(Error::InitError(e1), Error::InitError(e2)) => e1 == e2,
|
||||
(..) => false,
|
||||
}
|
||||
}
|
||||
@@ -241,6 +247,14 @@ impl From<FromSqlError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
impl From<ffi::InitError> for Error {
|
||||
#[cold]
|
||||
fn from(err: ffi::InitError) -> Error {
|
||||
Error::InitError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
@@ -311,6 +325,8 @@ impl fmt::Display for Error {
|
||||
ref sql,
|
||||
..
|
||||
} => write!(f, "{msg} in {sql} at offset {offset}"),
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
Error::InitError(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,6 +376,8 @@ impl error::Error for Error {
|
||||
Error::BlobSizeError => None,
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
Error::SqlInputError { ref error, .. } => Some(error),
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
Error::InitError(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,6 +385,7 @@ impl error::Error for Error {
|
||||
impl Error {
|
||||
/// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn sqlite_error(&self) -> Option<&ffi::Error> {
|
||||
match self {
|
||||
Self::SqliteFailure(error, _) => Some(error),
|
||||
@@ -377,6 +396,7 @@ impl Error {
|
||||
/// Returns the underlying SQLite error code if this is
|
||||
/// [`Error::SqliteFailure`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
|
||||
self.sqlite_error().map(|error| error.code)
|
||||
}
|
||||
@@ -439,10 +459,7 @@ pub fn check(code: c_int) -> Result<()> {
|
||||
/// Transform Rust error to SQLite error (message and code).
|
||||
/// # Safety
|
||||
/// This function is unsafe because it uses raw pointer
|
||||
pub unsafe fn to_sqlite_error(
|
||||
e: &Error,
|
||||
err_msg: *mut *mut std::os::raw::c_char,
|
||||
) -> std::os::raw::c_int {
|
||||
pub unsafe fn to_sqlite_error(e: &Error, err_msg: *mut *mut std::os::raw::c_char) -> c_int {
|
||||
use crate::util::alloc;
|
||||
match e {
|
||||
Error::SqliteFailure(err, s) => {
|
||||
|
||||
@@ -270,11 +270,11 @@ where
|
||||
/// call to [`step()`](Aggregate::step) to set up the context for an
|
||||
/// invocation of the function. (Note: `init()` will not be called if
|
||||
/// there are no rows.)
|
||||
fn init(&self, _: &mut Context<'_>) -> Result<A>;
|
||||
fn init(&self, ctx: &mut Context<'_>) -> Result<A>;
|
||||
|
||||
/// "step" function called once for each row in an aggregate group. May be
|
||||
/// called 0 times if there are no rows.
|
||||
fn step(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||
fn step(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
|
||||
|
||||
/// Computes and returns the final result. Will be called exactly once for
|
||||
/// each invocation of the function. If [`step()`](Aggregate::step) was
|
||||
@@ -285,7 +285,7 @@ where
|
||||
/// given `None`.
|
||||
///
|
||||
/// The passed context will have no arguments.
|
||||
fn finalize(&self, _: &mut Context<'_>, _: Option<A>) -> Result<(T, SubType)>;
|
||||
fn finalize(&self, ctx: &mut Context<'_>, acc: Option<A>) -> Result<(T, SubType)>;
|
||||
}
|
||||
|
||||
/// `WindowAggregate` is the callback interface for
|
||||
@@ -299,16 +299,17 @@ where
|
||||
{
|
||||
/// Returns the current value of the aggregate. Unlike xFinal, the
|
||||
/// implementation should not delete any context.
|
||||
fn value(&self, _: Option<&A>) -> Result<(T, SubType)>;
|
||||
fn value(&self, acc: Option<&mut A>) -> Result<(T, SubType)>;
|
||||
|
||||
/// Removes a row from the current window.
|
||||
fn inverse(&self, _: &mut Context<'_>, _: &mut A) -> Result<()>;
|
||||
fn inverse(&self, ctx: &mut Context<'_>, acc: &mut A) -> Result<()>;
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Function Flags.
|
||||
/// See [sqlite3_create_function](https://sqlite.org/c3ref/create_function.html)
|
||||
/// and [Function Flags](https://sqlite.org/c3ref/c_deterministic.html) for details.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct FunctionFlags: ::std::os::raw::c_int {
|
||||
/// Specifies UTF-8 as the text encoding this SQL function prefers for its parameters.
|
||||
@@ -327,6 +328,8 @@ bitflags::bitflags! {
|
||||
const SQLITE_SUBTYPE = 0x0000_0010_0000; // 3.30.0
|
||||
/// Means that the function is unlikely to cause problems even if misused.
|
||||
const SQLITE_INNOCUOUS = 0x0000_0020_0000; // 3.31.0
|
||||
/// Indicates to SQLite that a function might call `sqlite3_result_subtype()` to cause a sub-type to be associated with its result.
|
||||
const SQLITE_RESULT_SUBTYPE = 0x0000_0100_0000; // 3.45.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,6 +644,7 @@ unsafe extern "C" fn call_boxed_step<A, D, T>(
|
||||
args: slice::from_raw_parts(argv, argc as usize),
|
||||
};
|
||||
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if (*pac as *mut A).is_null() {
|
||||
*pac = Box::into_raw(Box::new((*boxed_aggr).init(&mut ctx)?));
|
||||
}
|
||||
@@ -711,7 +715,9 @@ where
|
||||
// Within the xFinal callback, it is customary to set N=0 in calls to
|
||||
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||
let a: Option<A> = match aggregate_context(ctx, 0) {
|
||||
Some(pac) => {
|
||||
Some(pac) =>
|
||||
{
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if (*pac as *mut A).is_null() {
|
||||
None
|
||||
} else {
|
||||
@@ -760,17 +766,10 @@ where
|
||||
{
|
||||
// Within the xValue callback, it is customary to set N=0 in calls to
|
||||
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||
let a: Option<&A> = match aggregate_context(ctx, 0) {
|
||||
Some(pac) => {
|
||||
if (*pac as *mut A).is_null() {
|
||||
None
|
||||
} else {
|
||||
let a = &**pac;
|
||||
Some(a)
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let pac = aggregate_context(ctx, 0).filter(|&pac| {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
!(*pac as *mut A).is_null()
|
||||
});
|
||||
|
||||
let r = catch_unwind(|| {
|
||||
let boxed_aggr: *mut W = ffi::sqlite3_user_data(ctx).cast::<W>();
|
||||
@@ -778,7 +777,7 @@ where
|
||||
!boxed_aggr.is_null(),
|
||||
"Internal error - null aggregate pointer"
|
||||
);
|
||||
(*boxed_aggr).value(a)
|
||||
(*boxed_aggr).value(pac.map(|pac| &mut **pac))
|
||||
});
|
||||
let t = match r {
|
||||
Err(_) => {
|
||||
@@ -1042,7 +1041,7 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn value(&self, sum: Option<&i64>) -> Result<(Option<i64>, SubType)> {
|
||||
fn value(&self, sum: Option<&mut i64>) -> Result<(Option<i64>, SubType)> {
|
||||
Ok((sum.copied(), None))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,9 +776,10 @@ mod test {
|
||||
.unwrap();
|
||||
|
||||
let authorizer = move |ctx: AuthContext<'_>| match ctx.action {
|
||||
AuthAction::Read { column_name, .. } if column_name == "private" => {
|
||||
Authorization::Ignore
|
||||
}
|
||||
AuthAction::Read {
|
||||
column_name: "private",
|
||||
..
|
||||
} => Authorization::Ignore,
|
||||
AuthAction::DropTable { .. } => Authorization::Deny,
|
||||
AuthAction::Pragma { .. } => panic!("shouldn't be called"),
|
||||
_ => Authorization::Allow,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::os::raw::{c_char, c_int};
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::ffi;
|
||||
@@ -40,7 +40,7 @@ pub struct InnerConnection {
|
||||
unsafe impl Send for InnerConnection {}
|
||||
|
||||
impl InnerConnection {
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
#[allow(clippy::mutex_atomic, clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle
|
||||
#[inline]
|
||||
pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection {
|
||||
InnerConnection {
|
||||
@@ -390,7 +390,7 @@ impl Drop for InnerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "wasm32")))]
|
||||
#[cfg(not(any(target_arch = "wasm32", feature = "loadable_extension")))]
|
||||
static SQLITE_INIT: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false);
|
||||
@@ -440,7 +440,9 @@ fn ensure_safe_sqlite_threading_mode() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "loadable_extension"))]
|
||||
SQLITE_INIT.call_once(|| {
|
||||
use std::sync::atomic::Ordering;
|
||||
if BYPASS_SQLITE_INIT.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
97
src/lib.rs
97
src/lib.rs
@@ -74,6 +74,7 @@ use crate::raw_statement::RawStatement;
|
||||
use crate::types::ValueRef;
|
||||
|
||||
pub use crate::cache::CachedStatement;
|
||||
#[cfg(feature = "column_decltype")]
|
||||
pub use crate::column::Column;
|
||||
pub use crate::error::{to_sqlite_error, Error};
|
||||
pub use crate::ffi::ErrorCode;
|
||||
@@ -82,9 +83,14 @@ pub use crate::load_extension_guard::LoadExtensionGuard;
|
||||
pub use crate::params::{params_from_iter, Params, ParamsFromIter};
|
||||
pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
|
||||
pub use crate::statement::{Statement, StatementStatus};
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
pub use crate::transaction::TransactionState;
|
||||
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
|
||||
pub use crate::types::ToSql;
|
||||
pub use crate::version::*;
|
||||
#[cfg(feature = "rusqlite-macros")]
|
||||
#[doc(hidden)]
|
||||
pub use rusqlite_macros::__bind;
|
||||
|
||||
mod error;
|
||||
|
||||
@@ -215,6 +221,51 @@ macro_rules! named_params {
|
||||
};
|
||||
}
|
||||
|
||||
/// Captured identifiers in SQL
|
||||
///
|
||||
/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not
|
||||
/// work).
|
||||
/// * `$x.y` expression does not work.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use rusqlite::{prepare_and_bind, Connection, Result, Statement};
|
||||
///
|
||||
/// fn misc(db: &Connection) -> Result<Statement> {
|
||||
/// let name = "Lisa";
|
||||
/// let age = 8;
|
||||
/// let smart = true;
|
||||
/// Ok(prepare_and_bind!(db, "SELECT $name, @age, :smart;"))
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "rusqlite-macros")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
|
||||
#[macro_export]
|
||||
macro_rules! prepare_and_bind {
|
||||
($conn:expr, $sql:literal) => {{
|
||||
let mut stmt = $conn.prepare($sql)?;
|
||||
$crate::__bind!(stmt $sql);
|
||||
stmt
|
||||
}};
|
||||
}
|
||||
|
||||
/// Captured identifiers in SQL
|
||||
///
|
||||
/// * only SQLite `$x` / `@x` / `:x` syntax works (Rust `&x` syntax does not
|
||||
/// work).
|
||||
/// * `$x.y` expression does not work.
|
||||
#[cfg(feature = "rusqlite-macros")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite-macros")))]
|
||||
#[macro_export]
|
||||
macro_rules! prepare_cached_and_bind {
|
||||
($conn:expr, $sql:literal) => {{
|
||||
let mut stmt = $conn.prepare_cached($sql)?;
|
||||
$crate::__bind!(stmt $sql);
|
||||
stmt
|
||||
}};
|
||||
}
|
||||
|
||||
/// A typedef of the result returned by many methods.
|
||||
pub type Result<T, E = Error> = result::Result<T, E>;
|
||||
|
||||
@@ -568,7 +619,7 @@ impl Connection {
|
||||
#[inline]
|
||||
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize> {
|
||||
self.prepare(sql)
|
||||
.and_then(|mut stmt| stmt.check_no_tail().and_then(|_| stmt.execute(params)))
|
||||
.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.
|
||||
@@ -651,7 +702,7 @@ impl Connection {
|
||||
|
||||
// https://sqlite.org/tclsqlite.html#onecolumn
|
||||
#[cfg(test)]
|
||||
pub(crate) fn one_column<T: crate::types::FromSql>(&self, sql: &str) -> Result<T> {
|
||||
pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
|
||||
self.query_row(sql, [], |r| r.get(0))
|
||||
}
|
||||
|
||||
@@ -901,6 +952,17 @@ impl Connection {
|
||||
})
|
||||
}
|
||||
|
||||
/// Like SQLITE_EXTENSION_INIT2 macro
|
||||
#[cfg(feature = "loadable_extension")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "loadable_extension")))]
|
||||
pub unsafe fn extension_init2(
|
||||
db: *mut ffi::sqlite3,
|
||||
p_api: *mut ffi::sqlite3_api_routines,
|
||||
) -> Result<Connection> {
|
||||
ffi::rusqlite_extension_init2(p_api)?;
|
||||
Connection::from_handle(db)
|
||||
}
|
||||
|
||||
/// Create a `Connection` from a raw owned handle.
|
||||
///
|
||||
/// The returned connection will attempt to close the inner connection
|
||||
@@ -912,7 +974,7 @@ impl Connection {
|
||||
/// This function is unsafe because improper use may impact the Connection.
|
||||
/// In particular, it should only be called on connections created
|
||||
/// and owned by the caller, e.g. as a result of calling
|
||||
/// ffi::sqlite3_open().
|
||||
/// `ffi::sqlite3_open`().
|
||||
#[inline]
|
||||
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
|
||||
let db = InnerConnection::new(db, true);
|
||||
@@ -1432,7 +1494,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "extra_check")]
|
||||
fn test_execute_select() {
|
||||
fn test_execute_select_with_no_row() {
|
||||
let db = checked_memory_handle();
|
||||
let err = db.execute("SELECT 1 WHERE 1 < ?1", [1i32]).unwrap_err();
|
||||
assert_eq!(
|
||||
@@ -1442,6 +1504,13 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_select_with_row() {
|
||||
let db = checked_memory_handle();
|
||||
let err = db.execute("SELECT 1", []).unwrap_err();
|
||||
assert_eq!(err, Error::ExecuteReturnedResults);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "extra_check")]
|
||||
fn test_execute_multiple() {
|
||||
@@ -1812,7 +1881,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_from_handle_owned() -> Result<()> {
|
||||
let mut handle: *mut ffi::sqlite3 = std::ptr::null_mut();
|
||||
let r = unsafe { ffi::sqlite3_open(":memory:\0".as_ptr() as *const i8, &mut handle) };
|
||||
let r = unsafe { ffi::sqlite3_open(":memory:\0".as_ptr() as *const c_char, &mut handle) };
|
||||
assert_eq!(r, ffi::SQLITE_OK);
|
||||
let db = unsafe { Connection::from_handle_owned(handle) }?;
|
||||
db.execute_batch("PRAGMA VACUUM")?;
|
||||
@@ -2114,9 +2183,25 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn db_readonly() -> Result<()> {
|
||||
fn db_readonly() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
assert!(!db.is_readonly(MAIN_DB)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "rusqlite-macros")]
|
||||
fn prepare_and_bind() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
let name = "Lisa";
|
||||
let age = 8;
|
||||
let mut stmt = prepare_and_bind!(db, "SELECT $name, $age;");
|
||||
let (v1, v2) = stmt
|
||||
.raw_query()
|
||||
.next()
|
||||
.and_then(|o| o.ok_or(Error::QueryReturnedNoRows))
|
||||
.and_then(|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?;
|
||||
assert_eq!((v1.as_str(), v2), (name, age));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::os::raw::c_int;
|
||||
/// See the official documentation for more information:
|
||||
/// - <https://www.sqlite.org/c3ref/c_limit_attached.html>
|
||||
/// - <https://www.sqlite.org/limits.html>
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
|
||||
|
||||
@@ -384,7 +384,6 @@ mod test {
|
||||
let mut rows = table_info.query(["sqlite_master"])?;
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
let row = row;
|
||||
let column: String = row.get(1)?;
|
||||
columns.push(column);
|
||||
}
|
||||
|
||||
47
src/row.rs
47
src/row.rs
@@ -14,9 +14,11 @@ pub struct Rows<'stmt> {
|
||||
|
||||
impl<'stmt> Rows<'stmt> {
|
||||
#[inline]
|
||||
fn reset(&mut self) {
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
if let Some(stmt) = self.stmt.take() {
|
||||
stmt.reset();
|
||||
stmt.reset()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +31,8 @@ impl<'stmt> Rows<'stmt> {
|
||||
/// This interface is not compatible with Rust's `Iterator` trait, because
|
||||
/// the lifetime of the returned row is tied to the lifetime of `self`.
|
||||
/// This is a fallible "streaming iterator". For a more natural interface,
|
||||
/// consider using [`query_map`](crate::Statement::query_map) or
|
||||
/// [`query_and_then`](crate::Statement::query_and_then) instead, which
|
||||
/// consider using [`query_map`](Statement::query_map) or
|
||||
/// [`query_and_then`](Statement::query_and_then) instead, which
|
||||
/// return types that implement `Iterator`.
|
||||
#[allow(clippy::should_implement_trait)] // cannot implement Iterator
|
||||
#[inline]
|
||||
@@ -105,6 +107,7 @@ impl<'stmt> Rows<'stmt> {
|
||||
}
|
||||
|
||||
impl Drop for Rows<'_> {
|
||||
#[allow(unused_must_use)]
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.reset();
|
||||
@@ -217,12 +220,12 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> {
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => {
|
||||
self.reset();
|
||||
let r = self.reset();
|
||||
self.row = None;
|
||||
Ok(())
|
||||
r
|
||||
}
|
||||
Err(e) => {
|
||||
self.reset();
|
||||
let _ = self.reset(); // prevents infinite loop on error
|
||||
self.row = None;
|
||||
Err(e)
|
||||
}
|
||||
@@ -247,7 +250,7 @@ pub struct Row<'stmt> {
|
||||
impl<'stmt> Row<'stmt> {
|
||||
/// Get the value of a particular column of the result row.
|
||||
///
|
||||
/// ## Failure
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if calling [`row.get(idx)`](Row::get) would return an error,
|
||||
/// including:
|
||||
@@ -330,7 +333,7 @@ impl<'stmt> Row<'stmt> {
|
||||
/// it can be difficult to use, and most callers will be better served by
|
||||
/// [`get`](Row::get) or [`get_unwrap`](Row::get_unwrap).
|
||||
///
|
||||
/// ## Failure
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if calling [`row.get_ref(idx)`](Row::get_ref) would return an
|
||||
/// error, including:
|
||||
@@ -585,4 +588,30 @@ mod tests {
|
||||
// We don't test one bigger because it's unimplemented
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bundled")]
|
||||
fn pathological_case() -> Result<()> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE foo(x);
|
||||
CREATE TRIGGER oops BEFORE INSERT ON foo BEGIN SELECT RAISE(FAIL, 'Boom'); END;",
|
||||
)?;
|
||||
let mut stmt = conn.prepare("INSERT INTO foo VALUES (0) RETURNING rowid;")?;
|
||||
{
|
||||
let iterator_count = stmt.query_map([], |_| Ok(()))?.count();
|
||||
assert_eq!(1, iterator_count); // should be 0
|
||||
use fallible_streaming_iterator::FallibleStreamingIterator;
|
||||
let fallible_iterator_count = stmt.query([])?.count().unwrap_or(0);
|
||||
assert_eq!(0, fallible_iterator_count);
|
||||
}
|
||||
{
|
||||
let iterator_last = stmt.query_map([], |_| Ok(()))?.last();
|
||||
assert!(iterator_last.is_some()); // should be none
|
||||
use fallible_iterator::FallibleIterator;
|
||||
let fallible_iterator_last = stmt.query([])?.map(|_| Ok(())).last();
|
||||
assert!(fallible_iterator_last.is_err());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ pub struct OwnedData {
|
||||
}
|
||||
|
||||
impl OwnedData {
|
||||
/// SAFETY: Caller must be certain that `ptr` is allocated by
|
||||
/// `sqlite3_malloc`.
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must be certain that `ptr` is allocated by `sqlite3_malloc`.
|
||||
pub unsafe fn from_raw_nonnull(ptr: NonNull<u8>, sz: usize) -> Self {
|
||||
Self { ptr, sz }
|
||||
}
|
||||
@@ -65,7 +66,7 @@ impl<'conn> Deref for Data<'conn> {
|
||||
|
||||
impl Connection {
|
||||
/// Serialize a database.
|
||||
pub fn serialize<'conn>(&'conn self, schema: DatabaseName<'_>) -> Result<Data<'conn>> {
|
||||
pub fn serialize(&self, schema: DatabaseName) -> Result<Data> {
|
||||
let schema = schema.as_cstring()?;
|
||||
let mut sz = 0;
|
||||
let mut ptr: *mut u8 = unsafe {
|
||||
@@ -142,7 +143,9 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
db.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
||||
let data = db.serialize(DatabaseName::Main)?;
|
||||
let Data::Owned(data) = data else { panic!("expected OwnedData")};
|
||||
let Data::Owned(data) = data else {
|
||||
panic!("expected OwnedData")
|
||||
};
|
||||
assert!(data.sz > 0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -152,7 +155,9 @@ mod test {
|
||||
let src = Connection::open_in_memory()?;
|
||||
src.execute_batch("CREATE TABLE x AS SELECT 'data'")?;
|
||||
let data = src.serialize(DatabaseName::Main)?;
|
||||
let Data::Owned(data) = data else { panic!("expected OwnedData")};
|
||||
let Data::Owned(data) = data else {
|
||||
panic!("expected OwnedData")
|
||||
};
|
||||
|
||||
let mut dst = Connection::open_in_memory()?;
|
||||
dst.deserialize(DatabaseName::Main, data, false)?;
|
||||
|
||||
@@ -405,7 +405,7 @@ impl Drop for ChangesetIter<'_> {
|
||||
}
|
||||
|
||||
/// An item passed to a conflict-handler by
|
||||
/// [`Connection::apply`](crate::Connection::apply), or an item generated by
|
||||
/// [`Connection::apply`](Connection::apply), or an item generated by
|
||||
/// [`ChangesetIter::next`](ChangesetIter::next).
|
||||
// TODO enum ? Delete, Insert, Update, ...
|
||||
pub struct ChangesetItem {
|
||||
|
||||
@@ -435,6 +435,10 @@ impl Statement<'_> {
|
||||
///
|
||||
/// Will return `None` if the column index is out of bounds or if the
|
||||
/// parameter is positional.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when parameter name is not valid UTF-8.
|
||||
#[inline]
|
||||
pub fn parameter_name(&self, index: usize) -> Option<&'_ str> {
|
||||
self.stmt.bind_parameter_name(index as i32).map(|name| {
|
||||
@@ -450,7 +454,7 @@ impl Statement<'_> {
|
||||
{
|
||||
let expected = self.stmt.bind_parameter_count();
|
||||
let mut index = 0;
|
||||
for p in params.into_iter() {
|
||||
for p in params {
|
||||
index += 1; // The leftmost SQL parameter has an index of 1.
|
||||
if index > expected {
|
||||
break;
|
||||
@@ -646,9 +650,12 @@ impl Statement<'_> {
|
||||
fn execute_with_bound_parameters(&mut self) -> Result<usize> {
|
||||
self.check_update()?;
|
||||
let r = self.stmt.step();
|
||||
self.stmt.reset();
|
||||
let rr = self.stmt.reset();
|
||||
match r {
|
||||
ffi::SQLITE_DONE => Ok(self.conn.changes() as usize),
|
||||
ffi::SQLITE_DONE => match rr {
|
||||
ffi::SQLITE_OK => Ok(self.conn.changes() as usize),
|
||||
_ => Err(self.conn.decode_result(rr).unwrap_err()),
|
||||
},
|
||||
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
|
||||
_ => Err(self.conn.decode_result(r).unwrap_err()),
|
||||
}
|
||||
@@ -744,7 +751,7 @@ impl Statement<'_> {
|
||||
|
||||
/// Reset all bindings
|
||||
pub fn clear_bindings(&mut self) {
|
||||
self.stmt.clear_bindings()
|
||||
self.stmt.clear_bindings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,8 +850,11 @@ impl Statement<'_> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn reset(&self) -> c_int {
|
||||
self.stmt.reset()
|
||||
pub(super) fn reset(&self) -> Result<()> {
|
||||
match self.stmt.reset() {
|
||||
ffi::SQLITE_OK => Ok(()),
|
||||
code => Err(self.conn.decode_result(code).unwrap_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1270,7 +1280,7 @@ mod test {
|
||||
assert_eq!(0, stmt.column_count());
|
||||
stmt.parameter_index("test").unwrap();
|
||||
stmt.step().unwrap_err();
|
||||
stmt.reset();
|
||||
stmt.reset().unwrap(); // SQLITE_OMIT_AUTORESET = false
|
||||
stmt.execute([]).unwrap_err();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::ffi;
|
||||
use crate::error::error_from_sqlite_code;
|
||||
use crate::{Connection, Result};
|
||||
use crate::Connection;
|
||||
|
||||
/// Set up the process-wide SQLite error logging callback.
|
||||
///
|
||||
@@ -25,7 +24,8 @@ use crate::{Connection, Result};
|
||||
/// * It must be threadsafe if SQLite is used in a multithreaded way.
|
||||
///
|
||||
/// cf [The Error And Warning Log](http://sqlite.org/errlog.html).
|
||||
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
|
||||
#[cfg(not(feature = "loadable_extension"))]
|
||||
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> crate::Result<()> {
|
||||
extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
|
||||
let c_slice = unsafe { CStr::from_ptr(msg).to_bytes() };
|
||||
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
|
||||
@@ -48,7 +48,7 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
|
||||
if rc == ffi::SQLITE_OK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error_from_sqlite_code(rc, None))
|
||||
Err(crate::error::error_from_sqlite_code(rc, None))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ impl Transaction<'_> {
|
||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
||||
};
|
||||
conn.execute_batch(query).map(move |_| Transaction {
|
||||
conn.execute_batch(query).map(move |()| Transaction {
|
||||
conn,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
})
|
||||
@@ -251,7 +251,7 @@ impl Savepoint<'_> {
|
||||
fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
|
||||
let name = name.into();
|
||||
conn.execute_batch(&format!("SAVEPOINT {name}"))
|
||||
.map(|_| Savepoint {
|
||||
.map(|()| Savepoint {
|
||||
conn,
|
||||
name,
|
||||
drop_behavior: DropBehavior::Rollback,
|
||||
@@ -346,8 +346,8 @@ impl Savepoint<'_> {
|
||||
match self.drop_behavior() {
|
||||
DropBehavior::Commit => self
|
||||
.commit_()
|
||||
.or_else(|_| self.rollback().and_then(|_| self.commit_())),
|
||||
DropBehavior::Rollback => self.rollback().and_then(|_| self.commit_()),
|
||||
.or_else(|_| self.rollback().and_then(|()| self.commit_())),
|
||||
DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()),
|
||||
DropBehavior::Ignore => Ok(()),
|
||||
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
||||
}
|
||||
@@ -471,8 +471,7 @@ impl Connection {
|
||||
///
|
||||
/// The savepoint defaults to rolling back when it is dropped. If you want
|
||||
/// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
|
||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::
|
||||
/// set_drop_behavior).
|
||||
/// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
||||
@@ -111,7 +111,7 @@ pub struct Null;
|
||||
|
||||
/// SQLite data types.
|
||||
/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
/// NULL
|
||||
Null,
|
||||
|
||||
@@ -36,8 +36,7 @@ impl ValueRef<'_> {
|
||||
|
||||
impl<'a> ValueRef<'a> {
|
||||
/// If `self` is case `Integer`, returns the integral value. Otherwise,
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_i64(&self) -> FromSqlResult<i64> {
|
||||
match *self {
|
||||
@@ -48,8 +47,7 @@ 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).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_i64_or_null(&self) -> FromSqlResult<Option<i64>> {
|
||||
match *self {
|
||||
@@ -60,8 +58,7 @@ impl<'a> ValueRef<'a> {
|
||||
}
|
||||
|
||||
/// If `self` is case `Real`, returns the floating point value. Otherwise,
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_f64(&self) -> FromSqlResult<f64> {
|
||||
match *self {
|
||||
@@ -72,8 +69,7 @@ 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).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_f64_or_null(&self) -> FromSqlResult<Option<f64>> {
|
||||
match *self {
|
||||
@@ -97,8 +93,7 @@ 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).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_str_or_null(&self) -> FromSqlResult<Option<&'a str>> {
|
||||
match *self {
|
||||
@@ -122,8 +117,7 @@ impl<'a> ValueRef<'a> {
|
||||
|
||||
/// If `self` is case `Null` returns None.
|
||||
/// If `self` is case `Blob`, returns the byte slice.
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::
|
||||
/// InvalidColumnType).
|
||||
/// Otherwise returns [`Err(Error::InvalidColumnType)`](crate::Error::InvalidColumnType).
|
||||
#[inline]
|
||||
pub fn as_blob_or_null(&self) -> FromSqlResult<Option<&'a [u8]>> {
|
||||
match *self {
|
||||
@@ -133,7 +127,7 @@ impl<'a> ValueRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte slice that makes up this ValueRef if it's either
|
||||
/// 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]> {
|
||||
|
||||
@@ -14,6 +14,10 @@ pub fn version_number() -> i32 {
|
||||
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
|
||||
///
|
||||
/// See [`sqlite3_libversion()`](https://www.sqlite.org/c3ref/libversion.html).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when version is not valid UTF-8.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn version() -> &'static str {
|
||||
|
||||
@@ -373,6 +373,7 @@ bitflags::bitflags! {
|
||||
/// Virtual table scan flags
|
||||
/// See [Function Flags](https://sqlite.org/c3ref/c_index_scan_unique.html) for details.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct IndexFlags: ::std::os::raw::c_int {
|
||||
/// Default
|
||||
const NONE = 0;
|
||||
|
||||
Reference in New Issue
Block a user