Merge pull request #824 from gwenn/progress_handler

Expose query progress information
This commit is contained in:
gwenn 2020-11-03 18:14:36 +01:00 committed by GitHub
commit b9ccb252ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 269 additions and 109 deletions

View File

@ -136,7 +136,10 @@
//! //!
//! // Insert another BLOB, this time using a parameter passed in from //! // Insert another BLOB, this time using a parameter passed in from
//! // rust (potentially with a dynamic size). //! // rust (potentially with a dynamic size).
//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?; //! db.execute(
//! "INSERT INTO test_table (content) VALUES (?)",
//! &[ZeroBlob(64)],
//! )?;
//! //!
//! // given a new row ID, we can reopen the blob on that row //! // given a new row ID, we can reopen the blob on that row
//! let rowid = db.last_insert_rowid(); //! let rowid = db.last_insert_rowid();
@ -177,7 +180,10 @@
//! //!
//! // Insert another blob, this time using a parameter passed in from //! // Insert another blob, this time using a parameter passed in from
//! // rust (potentially with a dynamic size). //! // rust (potentially with a dynamic size).
//! db.execute("INSERT INTO test_table (content) VALUES (?)", &[ZeroBlob(64)])?; //! db.execute(
//! "INSERT INTO test_table (content) VALUES (?)",
//! &[ZeroBlob(64)],
//! )?;
//! //!
//! // given a new row ID, we can reopen the blob on that row //! // given a new row ID, we can reopen the blob on that row
//! let rowid = db.last_insert_rowid(); //! let rowid = db.last_insert_rowid();
@ -196,8 +202,8 @@ use crate::{Connection, DatabaseName, Result};
mod pos_io; mod pos_io;
/// `feature = "blob"` Handle to an open BLOB. See [`rusqlite::blob`](crate::blob) documentation for /// `feature = "blob"` Handle to an open BLOB. See
/// in-depth discussion. /// [`rusqlite::blob`](crate::blob) documentation for in-depth discussion.
pub struct Blob<'conn> { pub struct Blob<'conn> {
conn: &'conn Connection, conn: &'conn Connection,
blob: *mut ffi::sqlite3_blob, blob: *mut ffi::sqlite3_blob,

View File

@ -137,9 +137,7 @@ mod test {
#[test] #[test]
#[ignore] // FIXME: unstable #[ignore] // FIXME: unstable
fn test_busy_handler() { fn test_busy_handler() {
lazy_static::lazy_static! { static CALLED: AtomicBool = AtomicBool::new(false);
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
fn busy_handler(_: i32) -> bool { fn busy_handler(_: i32) -> bool {
CALLED.store(true, Ordering::Relaxed); CALLED.store(true, Ordering::Relaxed);
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));

View File

@ -15,9 +15,9 @@ unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
impl Connection { impl Connection {
/// `feature = "collation"` Add or modify a collation. /// `feature = "collation"` Add or modify a collation.
pub fn create_collation<C>(&self, collation_name: &str, x_compare: C) -> Result<()> pub fn create_collation<'c, C>(&'c self, collation_name: &str, x_compare: C) -> Result<()>
where where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static, C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
{ {
self.db self.db
.borrow_mut() .borrow_mut()
@ -39,9 +39,9 @@ impl Connection {
} }
impl InnerConnection { impl InnerConnection {
fn create_collation<C>(&mut self, collation_name: &str, x_compare: C) -> Result<()> fn create_collation<'c, C>(&'c mut self, collation_name: &str, x_compare: C) -> Result<()>
where where
C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'static, C: Fn(&str, &str) -> Ordering + Send + UnwindSafe + 'c,
{ {
unsafe extern "C" fn call_boxed_closure<C>( unsafe extern "C" fn call_boxed_closure<C>(
arg1: *mut c_void, arg1: *mut c_void,

View File

@ -111,8 +111,9 @@ pub enum Error {
InvalidParameterCount(usize, usize), InvalidParameterCount(usize, usize),
/// Returned from various functions in the Blob IO positional API. For /// Returned from various functions in the Blob IO positional API. For
/// example, [`Blob::raw_read_at_exact`](crate::blob::Blob::raw_read_at_exact) /// example,
/// will return it if the blob has insufficient data. /// [`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(feature = "blob")]
BlobSizeError, BlobSizeError,
} }

View File

@ -22,8 +22,7 @@
//! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, //! FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
//! move |ctx| { //! move |ctx| {
//! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments"); //! assert_eq!(ctx.len(), 2, "called with unexpected number of arguments");
//! let regexp: Arc<Regex> = ctx //! let regexp: Arc<Regex> = ctx.get_or_create_aux(0, |vr| -> Result<_, BoxError> {
//! .get_or_create_aux(0, |vr| -> Result<_, BoxError> {
//! Ok(Regex::new(vr.as_str()?)?) //! Ok(Regex::new(vr.as_str()?)?)
//! })?; //! })?;
//! let is_match = { //! let is_match = {
@ -334,15 +333,15 @@ impl Connection {
/// # Failure /// # Failure
/// ///
/// Will return Err if the function could not be attached to the connection. /// Will return Err if the function could not be attached to the connection.
pub fn create_scalar_function<'a, F, T>( pub fn create_scalar_function<'c, F, T>(
&'a self, &'c self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
flags: FunctionFlags, flags: FunctionFlags,
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'a, F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
T: ToSql, T: ToSql,
{ {
self.db self.db
@ -411,15 +410,15 @@ impl Connection {
} }
impl InnerConnection { impl InnerConnection {
fn create_scalar_function<'a, F, T>( fn create_scalar_function<'c, F, T>(
&'a mut self, &'c mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
flags: FunctionFlags, flags: FunctionFlags,
x_func: F, x_func: F,
) -> Result<()> ) -> Result<()>
where where
F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'a, F: FnMut(&Context<'_>) -> Result<T> + Send + UnwindSafe + 'c,
T: ToSql, T: ToSql,
{ {
unsafe extern "C" fn call_boxed_closure<F, T>( unsafe extern "C" fn call_boxed_closure<F, T>(

View File

@ -2,7 +2,7 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::catch_unwind; use std::panic::{catch_unwind, RefUnwindSafe};
use std::ptr; use std::ptr;
use crate::ffi; use crate::ffi;
@ -40,9 +40,9 @@ impl Connection {
/// a transaction is committed. /// a transaction is committed.
/// ///
/// The callback returns `true` to rollback. /// The callback returns `true` to rollback.
pub fn commit_hook<F>(&self, hook: Option<F>) pub fn commit_hook<'c, F>(&'c self, hook: Option<F>)
where where
F: FnMut() -> bool + Send + 'static, F: FnMut() -> bool + Send + 'c,
{ {
self.db.borrow_mut().commit_hook(hook); self.db.borrow_mut().commit_hook(hook);
} }
@ -51,9 +51,9 @@ impl Connection {
/// a transaction is committed. /// a transaction is committed.
/// ///
/// The callback returns `true` to rollback. /// The callback returns `true` to rollback.
pub fn rollback_hook<F>(&self, hook: Option<F>) pub fn rollback_hook<'c, F>(&'c self, hook: Option<F>)
where where
F: FnMut() + Send + 'static, F: FnMut() + Send + 'c,
{ {
self.db.borrow_mut().rollback_hook(hook); self.db.borrow_mut().rollback_hook(hook);
} }
@ -68,12 +68,27 @@ impl Connection {
/// - the name of the database ("main", "temp", ...), /// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated, /// - the name of the table that is updated,
/// - the ROWID of the row that is updated. /// - the ROWID of the row that is updated.
pub fn update_hook<F>(&self, hook: Option<F>) pub fn update_hook<'c, F>(&'c self, hook: Option<F>)
where where
F: FnMut(Action, &str, &str, i64) + Send + 'static, F: FnMut(Action, &str, &str, i64) + Send + 'c,
{ {
self.db.borrow_mut().update_hook(hook); self.db.borrow_mut().update_hook(hook);
} }
/// `feature = "hooks"` 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
/// `handler`. If `num_ops` is less than one then the progress handler
/// is disabled.
///
/// If the progress callback returns `true`, the operation is interrupted.
pub fn progress_handler<F>(&self, num_ops: c_int, handler: Option<F>)
where
F: FnMut() -> bool + Send + RefUnwindSafe + 'static,
{
self.db.borrow_mut().progress_handler(num_ops, handler);
}
} }
impl InnerConnection { impl InnerConnection {
@ -81,11 +96,12 @@ impl InnerConnection {
self.update_hook(None::<fn(Action, &str, &str, i64)>); self.update_hook(None::<fn(Action, &str, &str, i64)>);
self.commit_hook(None::<fn() -> bool>); self.commit_hook(None::<fn() -> bool>);
self.rollback_hook(None::<fn()>); self.rollback_hook(None::<fn()>);
self.progress_handler(0, None::<fn() -> bool>);
} }
fn commit_hook<F>(&mut self, hook: Option<F>) fn commit_hook<'c, F>(&'c mut self, hook: Option<F>)
where where
F: FnMut() -> bool + Send + 'static, F: FnMut() -> bool + Send + 'c,
{ {
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where where
@ -132,9 +148,9 @@ impl InnerConnection {
self.free_commit_hook = free_commit_hook; self.free_commit_hook = free_commit_hook;
} }
fn rollback_hook<F>(&mut self, hook: Option<F>) fn rollback_hook<'c, F>(&'c mut self, hook: Option<F>)
where where
F: FnMut() + Send + 'static, F: FnMut() + Send + 'c,
{ {
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where where
@ -173,9 +189,9 @@ impl InnerConnection {
self.free_rollback_hook = free_rollback_hook; self.free_rollback_hook = free_rollback_hook;
} }
fn update_hook<F>(&mut self, hook: Option<F>) fn update_hook<'c, F>(&'c mut self, hook: Option<F>)
where where
F: FnMut(Action, &str, &str, i64) + Send + 'static, F: FnMut(Action, &str, &str, i64) + Send + 'c,
{ {
unsafe extern "C" fn call_boxed_closure<F>( unsafe extern "C" fn call_boxed_closure<F>(
p_arg: *mut c_void, p_arg: *mut c_void,
@ -236,6 +252,45 @@ impl InnerConnection {
} }
self.free_update_hook = free_update_hook; self.free_update_hook = free_update_hook;
} }
fn progress_handler<F>(&mut self, num_ops: c_int, handler: Option<F>)
where
F: FnMut() -> bool + Send + RefUnwindSafe + '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_handler: *mut F = p_arg as *mut F;
(*boxed_handler)()
});
if let Ok(true) = r {
1
} else {
0
}
}
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;
}
};
}
} }
unsafe fn free_boxed_hook<F>(p: *mut c_void) { unsafe fn free_boxed_hook<F>(p: *mut c_void) {
@ -246,23 +301,20 @@ unsafe fn free_boxed_hook<F>(p: *mut c_void) {
mod test { mod test {
use super::Action; use super::Action;
use crate::Connection; use crate::Connection;
use lazy_static::lazy_static;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
#[test] #[test]
fn test_commit_hook() { fn test_commit_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { let mut called = false;
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.commit_hook(Some(|| { db.commit_hook(Some(|| {
CALLED.store(true, Ordering::Relaxed); called = true;
false false
})); }));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap(); .unwrap();
assert!(CALLED.load(Ordering::Relaxed)); assert!(called);
} }
#[test] #[test]
@ -282,33 +334,59 @@ mod test {
fn test_rollback_hook() { fn test_rollback_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { let mut called = false;
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.rollback_hook(Some(|| { db.rollback_hook(Some(|| {
CALLED.store(true, Ordering::Relaxed); called = true;
})); }));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;") db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
.unwrap(); .unwrap();
assert!(CALLED.load(Ordering::Relaxed)); assert!(called);
} }
#[test] #[test]
fn test_update_hook() { fn test_update_hook() {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
lazy_static! { let mut called = false;
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.update_hook(Some(|action, db: &str, tbl: &str, row_id| { db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
assert_eq!(Action::SQLITE_INSERT, action); assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db); assert_eq!("main", db);
assert_eq!("foo", tbl); assert_eq!("foo", tbl);
assert_eq!(1, row_id); assert_eq!(1, row_id);
CALLED.store(true, Ordering::Relaxed); called = true;
})); }));
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap(); db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap(); db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
assert!(called);
}
#[test]
fn test_progress_handler() {
let db = Connection::open_in_memory().unwrap();
static CALLED: AtomicBool = AtomicBool::new(false);
db.progress_handler(
1,
Some(|| {
CALLED.store(true, Ordering::Relaxed);
false
}),
);
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap();
assert!(CALLED.load(Ordering::Relaxed)); assert!(CALLED.load(Ordering::Relaxed));
} }
#[test]
fn test_progress_handler_interrupt() {
let db = Connection::open_in_memory().unwrap();
fn handler() -> bool {
true
}
db.progress_handler(1, Some(handler));
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap_err();
}
} }

View File

@ -31,6 +31,8 @@ pub struct InnerConnection {
pub free_rollback_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>, pub free_rollback_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>,
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
pub free_update_hook: Option<unsafe fn(*mut ::std::os::raw::c_void)>, 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>>,
owned: bool, owned: bool,
} }
@ -46,6 +48,8 @@ impl InnerConnection {
free_rollback_hook: None, free_rollback_hook: None,
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
free_update_hook: None, free_update_hook: None,
#[cfg(feature = "hooks")]
progress_handler: None,
owned, owned,
} }
} }

View File

@ -810,6 +810,67 @@ impl fmt::Debug for Connection {
} }
} }
/// Batch iterator
/// ```rust
/// use rusqlite::{Batch, Connection, Result, NO_PARAMS};
///
/// fn main() -> Result<()> {
/// let conn = Connection::open_in_memory()?;
/// let sql = r"
/// CREATE TABLE tbl1 (col);
/// CREATE TABLE tbl2 (col);
/// ";
/// let mut batch = Batch::new(&conn, sql);
/// while let Some(mut stmt) = batch.next()? {
/// stmt.execute(NO_PARAMS)?;
/// }
/// Ok(())
/// }
/// ```
#[derive(Debug)]
pub struct Batch<'conn, 'sql> {
conn: &'conn Connection,
sql: &'sql str,
tail: usize,
}
impl<'conn, 'sql> Batch<'conn, 'sql> {
/// Constructor
pub fn new(conn: &'conn Connection, sql: &'sql str) -> Batch<'conn, 'sql> {
Batch { conn, sql, tail: 0 }
}
/// Iterates on each batch statements.
///
/// Returns `Ok(None)` when batch is completed.
#[allow(clippy::should_implement_trait)] // fallible iterator
pub fn next(&mut self) -> Result<Option<Statement<'conn>>> {
while self.tail < self.sql.len() {
let sql = &self.sql[self.tail..];
let next = self.conn.prepare(sql)?;
let tail = next.stmt.tail();
if tail == 0 {
self.tail = self.sql.len();
} else {
self.tail += tail;
}
if next.stmt.is_null() {
continue;
}
return Ok(Some(next));
}
Ok(None)
}
}
impl<'conn> Iterator for Batch<'conn, '_> {
type Item = Result<Statement<'conn>>;
fn next(&mut self) -> Option<Result<Statement<'conn>>> {
self.next().transpose()
}
}
bitflags::bitflags! { bitflags::bitflags! {
/// Flags for opening SQLite database connections. /// Flags for opening SQLite database connections.
/// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details. /// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
@ -1749,6 +1810,7 @@ mod test {
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
} }
}
#[test] #[test]
fn test_dynamic() { fn test_dynamic() {
@ -1759,7 +1821,7 @@ mod test {
END;"; END;";
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
db.query_row("SELECT * FROM foo", params![], |r| { db.query_row("SELECT * FROM foo", NO_PARAMS, |r| {
assert_eq!(2, r.column_count()); assert_eq!(2, r.column_count());
Ok(()) Ok(())
}) })
@ -1771,7 +1833,7 @@ mod test {
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let b: Box<dyn ToSql> = Box::new(5); let b: Box<dyn ToSql> = Box::new(5);
db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap(); db.execute("INSERT INTO foo VALUES(?)", &[b]).unwrap();
db.query_row("SELECT x FROM foo", params![], |r| { db.query_row("SELECT x FROM foo", NO_PARAMS, |r| {
assert_eq!(5, r.get_unwrap::<_, i32>(0)); assert_eq!(5, r.get_unwrap::<_, i32>(0));
Ok(()) Ok(())
}) })
@ -1788,8 +1850,8 @@ mod test {
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?;", ?, ?, ?, ?;",
params![ params![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
], ],
|r| { |r| {
assert_eq!(1, r.get_unwrap::<_, i32>(0)); assert_eq!(1, r.get_unwrap::<_, i32>(0));
@ -1805,7 +1867,20 @@ mod test {
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute_batch("CREATE TABLE x(t);").unwrap(); db.execute_batch("CREATE TABLE x(t);").unwrap();
// `execute_batch` should be used but `execute` should also work // `execute_batch` should be used but `execute` should also work
db.execute("ALTER TABLE x RENAME TO y;", params![]).unwrap(); db.execute("ALTER TABLE x RENAME TO y;", NO_PARAMS).unwrap();
}
#[test]
fn test_batch() {
let db = checked_memory_handle();
let sql = r"
CREATE TABLE tbl1 (col);
CREATE TABLE tbl2 (col);
";
let batch = Batch::new(&db, sql);
for stmt in batch {
let mut stmt = stmt.unwrap();
stmt.execute(NO_PARAMS).unwrap();
} }
} }
} }

View File

@ -819,9 +819,7 @@ mod test {
db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);") db.execute_batch("CREATE TABLE foo(t TEXT PRIMARY KEY NOT NULL);")
.unwrap(); .unwrap();
lazy_static::lazy_static! { static CALLED: AtomicBool = AtomicBool::new(false);
static ref CALLED: AtomicBool = AtomicBool::new(false);
}
db.apply( db.apply(
&changeset, &changeset,
None::<fn(&str) -> bool>, None::<fn(&str) -> bool>,

View File

@ -15,8 +15,8 @@
//! `FromSql` has different behaviour depending on the SQL and Rust types, and //! `FromSql` has different behaviour depending on the SQL and Rust types, and
//! the value. //! the value.
//! //!
//! * `INTEGER` to integer: returns an `Error::IntegralValueOutOfRange` error //! * `INTEGER` to integer: returns an `Error::IntegralValueOutOfRange` error if
//! if the value does not fit in the Rust type. //! the value does not fit in the Rust type.
//! * `REAL` to integer: always returns an `Error::InvalidColumnType` error. //! * `REAL` to integer: always returns an `Error::InvalidColumnType` error.
//! * `INTEGER` to float: casts using `as` operator. Never fails. //! * `INTEGER` to float: casts using `as` operator. Never fails.
//! * `REAL` to float: casts using `as` operator. Never fails. //! * `REAL` to float: casts using `as` operator. Never fails.
@ -32,7 +32,6 @@
//! can be parsed by SQLite's builtin //! can be parsed by SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you //! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
//! want different storage for datetimes, you can use a newtype. //! want different storage for datetimes, you can use a newtype.
//!
#![cfg_attr( #![cfg_attr(
feature = "time", feature = "time",
doc = r##" doc = r##"

View File

@ -100,6 +100,7 @@ impl std::fmt::Debug for SmallCString {
impl std::ops::Deref for SmallCString { impl std::ops::Deref for SmallCString {
type Target = CStr; type Target = CStr;
#[inline] #[inline]
fn deref(&self) -> &CStr { fn deref(&self) -> &CStr {
self.as_cstr() self.as_cstr()

View File

@ -130,9 +130,10 @@ impl SqliteMallocString {
// This is safe: // This is safe:
// - `align` is never 0 // - `align` is never 0
// - `align` is always a power of 2. // - `align` is always a power of 2.
// - `size` needs no realignment because it's guaranteed to be // - `size` needs no realignment because it's guaranteed to be aligned
// aligned (everything is aligned to 1) // (everything is aligned to 1)
// - `size` is also never zero, although this function doesn't actually require it now. // - `size` is also never zero, although this function doesn't actually require
// it now.
let layout = Layout::from_size_align_unchecked(s.len().saturating_add(1), 1); let layout = Layout::from_size_align_unchecked(s.len().saturating_add(1), 1);
// Note: This call does not return. // Note: This call does not return.
handle_alloc_error(layout); handle_alloc_error(layout);