Merge branch 'master' of https://github.com/jgallagher/rusqlite into functions

This commit is contained in:
Gwenael Treguier 2015-12-06 21:33:21 +01:00
commit fe6afe2a94
12 changed files with 699 additions and 80 deletions

View File

@ -1,4 +1,5 @@
language: rust language: rust
sudo: false
env: env:
global: global:

View File

@ -5,3 +5,8 @@ rusqlite contributors (sorted alphabetically)
* [Marcus Klaas de Vries](https://github.com/marcusklaas) * [Marcus Klaas de Vries](https://github.com/marcusklaas)
* [gwenn](https://github.com/gwenn) * [gwenn](https://github.com/gwenn)
* [Jimmy Lu](https://github.com/Yuhta) * [Jimmy Lu](https://github.com/Yuhta)
* [Huon Wilson](https://github.com/huonw)
* [Patrick Fernie](https://github.com/pfernie)
* [Steve Klabnik](https://github.com/steveklabnik)
* [krdln](https://github.com/krdln)
* [Ben Striegel](https://github.com/bstrie)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.2.0" version = "0.4.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
@ -15,16 +15,22 @@ name = "rusqlite"
[features] [features]
load_extension = ["libsqlite3-sys/load_extension"] load_extension = ["libsqlite3-sys/load_extension"]
functions = [] functions = []
trace = []
[dependencies] [dependencies]
time = "~0.1.0" time = "~0.1.0"
bitflags = "~0.1" bitflags = "~0.1"
libc = "~0.1" libc = "~0.2"
[dev-dependencies] [dev-dependencies]
tempdir = "~0.3.4" tempdir = "~0.3.4"
lazy_static = "~0.1"
regex = "~0.1.41" regex = "~0.1.41"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
version = "0.2.0" version = "0.2.0"
[[test]]
name = "config_log"
harness = false

View File

@ -1,3 +1,31 @@
# Version UPCOMING (TBD)
* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
* Slight change to the closure types passed to `query_map` and `query_and_then`:
* Remove the `'static` requirement on the closure's output type.
* Give the closure a `&SqliteRow` instead of a `SqliteRow`.
* Add more documentation for failure modes of functions that return `SqliteResult`s.
# Version 0.4.0 (2015-11-03)
* Adds `Sized` bound to `FromSql` trait as required by RFC 1214.
# Version 0.3.1 (2015-09-22)
* Reset underlying SQLite statements as soon as possible after executing, as recommended by
http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor.
# Version 0.3.0 (2015-09-21)
* Removes `get_opt`. Use `get_checked` instead.
* Add `query_row_and_then` and `query_and_then` convenience functions. These are analogous to
`query_row` and `query_map` but allow functions that can fail by returning `Result`s.
* Relax uses of `P: AsRef<...>` from `&P` to `P`.
* Add additional error check for calling `execute` when `query` was intended.
* Improve debug formatting of `SqliteStatement` and `SqliteConnection`.
* Changes documentation of `get_checked` to correctly indicate that it returns errors (not panics)
when given invalid types or column indices.
# Version 0.2.0 (2015-07-26) # Version 0.2.0 (2015-07-26)
* Add `column_names()` to `SqliteStatement`. * Add `column_names()` to `SqliteStatement`.

View File

@ -91,7 +91,7 @@ There are other, less obvious things that may result in a panic as well, such as
`collect()` on a `SqliteRows` and then trying to use the collected rows. `collect()` on a `SqliteRows` and then trying to use the collected rows.
Strongly consider using the method `query_map()` instead, if you can. Strongly consider using the method `query_map()` instead, if you can.
`query_map()` returns an iterator over rows-mapped-to-some-`'static`-type. This `query_map()` returns an iterator over rows-mapped-to-some-type. This
iterator does not have any of the above issues with panics due to attempting to iterator does not have any of the above issues with panics due to attempting to
access stale rows. access stale rows.

View File

@ -15,4 +15,4 @@ load_extension = []
pkg-config = "~0.3" pkg-config = "~0.3"
[dependencies] [dependencies]
libc = "~0.1" libc = "~0.2"

View File

@ -93,5 +93,6 @@ pub fn code_to_str(code: c_int) -> &'static str {
} }
} }
pub const SQLITE_CONFIG_LOG : c_int = 16;
pub const SQLITE_UTF8 : c_int = 1; pub const SQLITE_UTF8 : c_int = 1;
pub const SQLITE_DETERMINISTIC : c_int = 0x800; pub const SQLITE_DETERMINISTIC : c_int = 0x800;

View File

@ -106,7 +106,7 @@ impl ToResult for Null {
// sqlite3_result_value // sqlite3_result_value
/// A trait for types that can be created from a SQLite function parameter value. /// A trait for types that can be created from a SQLite function parameter value.
pub trait FromValue { pub trait FromValue: Sized {
unsafe fn parameter_value(v: *mut sqlite3_value) -> SqliteResult<Self>; unsafe fn parameter_value(v: *mut sqlite3_value) -> SqliteResult<Self>;
/// FromValue types can implement this method and use sqlite3_value_type to check that /// FromValue types can implement this method and use sqlite3_value_type to check that

View File

@ -50,16 +50,17 @@
//! } //! }
//! } //! }
//! ``` //! ```
#![cfg_attr(test, feature(box_raw))]
extern crate libc; extern crate libc;
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
#[macro_use] extern crate bitflags; #[macro_use] extern crate bitflags;
#[cfg(test)] #[macro_use] extern crate lazy_static;
use std::default::Default; use std::default::Default;
use std::convert;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::fmt; use std::fmt;
use std::path::{Path}; use std::path::{Path,PathBuf};
use std::error; use std::error;
use std::rc::{Rc}; use std::rc::{Rc};
use std::cell::{RefCell, Cell}; use std::cell::{RefCell, Cell};
@ -81,6 +82,7 @@ pub mod types;
mod transaction; mod transaction;
#[cfg(feature = "load_extension")] mod load_extension_guard; #[cfg(feature = "load_extension")] mod load_extension_guard;
#[cfg(feature = "functions")] pub mod functions; #[cfg(feature = "functions")] pub mod functions;
#[cfg(feature = "trace")] pub mod trace;
/// A typedef of the result returned by many methods. /// A typedef of the result returned by many methods.
pub type SqliteResult<T> = Result<T, SqliteError>; pub type SqliteResult<T> = Result<T, SqliteError>;
@ -92,7 +94,7 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
} }
/// Encompasses an error result from a call to the SQLite C API. /// Encompasses an error result from a call to the SQLite C API.
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct SqliteError { pub struct SqliteError {
/// The error code returned by a SQLite C API call. See [SQLite Result /// The error code returned by a SQLite C API call. See [SQLite Result
/// Codes](http://www.sqlite.org/rescode.html) for details. /// Codes](http://www.sqlite.org/rescode.html) for details.
@ -129,30 +131,22 @@ impl SqliteError {
fn str_to_cstring(s: &str) -> SqliteResult<CString> { fn str_to_cstring(s: &str) -> SqliteResult<CString> {
CString::new(s).map_err(|_| SqliteError{ CString::new(s).map_err(|_| SqliteError{
code: ffi::SQLITE_MISUSE, code: ffi::SQLITE_MISUSE,
message: "Could not convert path to C-combatible string".to_string() message: format!("Could not convert string {} to C-combatible string", s),
}) })
} }
fn path_to_cstring(p: &Path) -> SqliteResult<CString> { fn path_to_cstring(p: &Path) -> SqliteResult<CString> {
let s = try!(p.to_str().ok_or(SqliteError{ let s = try!(p.to_str().ok_or(SqliteError{
code: ffi::SQLITE_MISUSE, code: ffi::SQLITE_MISUSE,
message: "Could not convert path to UTF-8 string".to_string() message: format!("Could not convert path {} to UTF-8 string", p.to_string_lossy()),
})); }));
str_to_cstring(s) str_to_cstring(s)
} }
/// A connection to a SQLite database. /// A connection to a SQLite database.
///
/// ## Warning
///
/// Note that despite the fact that most `SqliteConnection` methods take an immutable reference to
/// `self`, `SqliteConnection` is NOT threadsafe, and using it from multiple threads may result in
/// runtime panics or data races. The SQLite connection handle has at least two pieces of internal
/// state (the last insertion ID and the last error message) that rusqlite uses, but wrapping these
/// APIs in a safe way from Rust would be too restrictive (for example, you would not be able to
/// prepare multiple statements at the same time).
pub struct SqliteConnection { pub struct SqliteConnection {
db: RefCell<InnerSqliteConnection>, db: RefCell<InnerSqliteConnection>,
path: Option<PathBuf>,
} }
unsafe impl Send for SqliteConnection {} unsafe impl Send for SqliteConnection {}
@ -162,12 +156,21 @@ impl SqliteConnection {
/// ///
/// `SqliteConnection::open(path)` is equivalent to `SqliteConnection::open_with_flags(path, /// `SqliteConnection::open(path)` is equivalent to `SqliteConnection::open_with_flags(path,
/// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`. /// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`.
pub fn open<P: AsRef<Path>>(path: &P) -> SqliteResult<SqliteConnection> { ///
/// # Failure
///
/// Will return `Err` if `path` cannot be converted to a C-compatible string or if the
/// underlying SQLite open call fails.
pub fn open<P: AsRef<Path>>(path: P) -> SqliteResult<SqliteConnection> {
let flags = Default::default(); let flags = Default::default();
SqliteConnection::open_with_flags(path, flags) SqliteConnection::open_with_flags(path, flags)
} }
/// Open a new connection to an in-memory SQLite database. /// Open a new connection to an in-memory SQLite database.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory() -> SqliteResult<SqliteConnection> { pub fn open_in_memory() -> SqliteResult<SqliteConnection> {
let flags = Default::default(); let flags = Default::default();
SqliteConnection::open_in_memory_with_flags(flags) SqliteConnection::open_in_memory_with_flags(flags)
@ -177,11 +180,16 @@ impl SqliteConnection {
/// ///
/// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid /// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
/// flag combinations. /// flag combinations.
pub fn open_with_flags<P: AsRef<Path>>(path: &P, flags: SqliteOpenFlags) ///
/// # Failure
///
/// Will return `Err` if `path` cannot be converted to a C-compatible string or if the
/// underlying SQLite open call fails.
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: SqliteOpenFlags)
-> SqliteResult<SqliteConnection> { -> SqliteResult<SqliteConnection> {
let c_path = try!(path_to_cstring(path.as_ref())); let c_path = try!(path_to_cstring(path.as_ref()));
InnerSqliteConnection::open_with_flags(&c_path, flags).map(|db| { InnerSqliteConnection::open_with_flags(&c_path, flags).map(|db| {
SqliteConnection{ db: RefCell::new(db) } SqliteConnection{ db: RefCell::new(db), path: Some(path.as_ref().to_path_buf()) }
}) })
} }
@ -189,10 +197,14 @@ impl SqliteConnection {
/// ///
/// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid /// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
/// flag combinations. /// flag combinations.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory_with_flags(flags: SqliteOpenFlags) -> SqliteResult<SqliteConnection> { pub fn open_in_memory_with_flags(flags: SqliteOpenFlags) -> SqliteResult<SqliteConnection> {
let c_memory = try!(str_to_cstring(":memory:")); let c_memory = try!(str_to_cstring(":memory:"));
InnerSqliteConnection::open_with_flags(&c_memory, flags).map(|db| { InnerSqliteConnection::open_with_flags(&c_memory, flags).map(|db| {
SqliteConnection{ db: RefCell::new(db) } SqliteConnection{ db: RefCell::new(db), path: None }
}) })
} }
@ -216,6 +228,10 @@ impl SqliteConnection {
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> { pub fn transaction<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, SqliteTransactionDeferred) SqliteTransaction::new(self, SqliteTransactionDeferred)
} }
@ -223,6 +239,10 @@ impl SqliteConnection {
/// Begin a new transaction with a specified behavior. /// Begin a new transaction with a specified behavior.
/// ///
/// See `transaction`. /// See `transaction`.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior<'a>(&'a self, behavior: SqliteTransactionBehavior) pub fn transaction_with_behavior<'a>(&'a self, behavior: SqliteTransactionBehavior)
-> SqliteResult<SqliteTransaction<'a>> { -> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, behavior) SqliteTransaction::new(self, behavior)
@ -243,6 +263,11 @@ impl SqliteConnection {
/// COMMIT;") /// COMMIT;")
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute_batch(&self, sql: &str) -> SqliteResult<()> { pub fn execute_batch(&self, sql: &str) -> SqliteResult<()> {
self.db.borrow_mut().execute_batch(sql) self.db.borrow_mut().execute_batch(sql)
} }
@ -263,6 +288,11 @@ impl SqliteConnection {
/// } /// }
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> SqliteResult<c_int> { pub fn execute(&self, sql: &str, params: &[&ToSql]) -> SqliteResult<c_int> {
self.prepare(sql).and_then(|mut stmt| stmt.execute(params)) self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
} }
@ -289,6 +319,11 @@ impl SqliteConnection {
/// ``` /// ```
/// ///
/// If the query returns more than one row, all rows except the first are ignored. /// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> SqliteResult<T> pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> SqliteResult<T>
where F: FnOnce(SqliteRow) -> T { where F: FnOnce(SqliteRow) -> T {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
@ -303,6 +338,42 @@ impl SqliteConnection {
} }
} }
/// Convenience method to execute a query that is expected to return a single row,
/// and execute a mapping via `f` on that returned row with the possibility of failure.
/// The `Result` type of `f` must implement `std::convert::From<SqliteError>`.
///
/// ## Example
///
/// ```rust,no_run
/// # use rusqlite::{SqliteResult,SqliteConnection};
/// fn preferred_locale(conn: &SqliteConnection) -> SqliteResult<String> {
/// conn.query_row_and_then("SELECT value FROM preferences WHERE name='locale'", &[], |row| {
/// row.get_checked(0)
/// })
/// }
/// ```
///
/// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row_and_then<T, E, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T, E>
where F: FnOnce(SqliteRow) -> Result<T, E>,
E: convert::From<SqliteError> {
let mut stmt = try!(self.prepare(sql));
let mut rows = try!(stmt.query(params));
match rows.next() {
Some(row) => row.map_err(E::from).and_then(f),
None => Err(E::from(SqliteError{
code: ffi::SQLITE_NOTICE,
message: "Query did not return a row".to_string(),
}))
}
}
/// Convenience method to execute a query that is expected to return a single row. /// Convenience method to execute a query that is expected to return a single row.
/// ///
/// ## Example /// ## Example
@ -340,6 +411,11 @@ impl SqliteConnection {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn prepare<'a>(&'a self, sql: &str) -> SqliteResult<SqliteStatement<'a>> { pub fn prepare<'a>(&'a self, sql: &str) -> SqliteResult<SqliteStatement<'a>> {
self.db.borrow_mut().prepare(self, sql) self.db.borrow_mut().prepare(self, sql)
} }
@ -348,6 +424,10 @@ impl SqliteConnection {
/// ///
/// This is functionally equivalent to the `Drop` implementation for `SqliteConnection` except /// This is functionally equivalent to the `Drop` implementation for `SqliteConnection` except
/// that it returns any error encountered to the caller. /// that it returns any error encountered to the caller.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn close(self) -> SqliteResult<()> { pub fn close(self) -> SqliteResult<()> {
let mut db = self.db.borrow_mut(); let mut db = self.db.borrow_mut();
db.close() db.close()
@ -367,6 +447,10 @@ impl SqliteConnection {
/// conn.load_extension_disable() /// conn.load_extension_disable()
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension_enable(&self) -> SqliteResult<()> { pub fn load_extension_enable(&self) -> SqliteResult<()> {
self.db.borrow_mut().enable_load_extension(1) self.db.borrow_mut().enable_load_extension(1)
@ -375,6 +459,10 @@ impl SqliteConnection {
/// Disable loading of SQLite extensions. /// Disable loading of SQLite extensions.
/// ///
/// See `load_extension_enable` for an example. /// See `load_extension_enable` for an example.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension_disable(&self) -> SqliteResult<()> { pub fn load_extension_disable(&self) -> SqliteResult<()> {
self.db.borrow_mut().enable_load_extension(0) self.db.borrow_mut().enable_load_extension(0)
@ -395,10 +483,14 @@ impl SqliteConnection {
/// fn load_my_extension(conn: &SqliteConnection) -> SqliteResult<()> { /// fn load_my_extension(conn: &SqliteConnection) -> SqliteResult<()> {
/// let _guard = try!(SqliteLoadExtensionGuard::new(conn)); /// let _guard = try!(SqliteLoadExtensionGuard::new(conn));
/// ///
/// conn.load_extension(Path::new("my_sqlite_extension"), None) /// conn.load_extension("my_sqlite_extension", None)
/// } /// }
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension<P: AsRef<Path>>(&self, dylib_path: &P, entry_point: Option<&str>) -> SqliteResult<()> { pub fn load_extension<P: AsRef<Path>>(&self, dylib_path: P, entry_point: Option<&str>) -> SqliteResult<()> {
self.db.borrow_mut().load_extension(dylib_path, entry_point) self.db.borrow_mut().load_extension(dylib_path, entry_point)
} }
@ -413,7 +505,9 @@ impl SqliteConnection {
impl fmt::Debug for SqliteConnection { impl fmt::Debug for SqliteConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SqliteConnection()") f.debug_struct("SqliteConnection")
.field("path", &self.path)
.finish()
} }
} }
@ -544,6 +638,12 @@ impl InnerSqliteConnection {
fn prepare<'a>(&mut self, fn prepare<'a>(&mut self,
conn: &'a SqliteConnection, conn: &'a SqliteConnection,
sql: &str) -> SqliteResult<SqliteStatement<'a>> { sql: &str) -> SqliteResult<SqliteStatement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(SqliteError {
code: ffi::SQLITE_TOOBIG,
message: "statement too long".to_string()
});
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = try!(str_to_cstring(sql)); let c_sql = try!(str_to_cstring(sql));
let r = unsafe { let r = unsafe {
@ -573,16 +673,18 @@ pub struct SqliteStatement<'conn> {
conn: &'conn SqliteConnection, conn: &'conn SqliteConnection,
stmt: *mut ffi::sqlite3_stmt, stmt: *mut ffi::sqlite3_stmt,
needs_reset: bool, needs_reset: bool,
column_count: c_int,
} }
impl<'conn> SqliteStatement<'conn> { impl<'conn> SqliteStatement<'conn> {
fn new(conn: &SqliteConnection, stmt: *mut ffi::sqlite3_stmt) -> SqliteStatement { fn new(conn: &SqliteConnection, stmt: *mut ffi::sqlite3_stmt) -> SqliteStatement {
SqliteStatement{ conn: conn, stmt: stmt, needs_reset: false } SqliteStatement{ conn: conn, stmt: stmt, needs_reset: false,
column_count: unsafe { ffi::sqlite3_column_count(stmt) }}
} }
/// Get all the column names in the result set of the prepared statement. /// Get all the column names in the result set of the prepared statement.
pub fn column_names(&self) -> Vec<&str> { pub fn column_names(&self) -> Vec<&str> {
let n = unsafe { ffi::sqlite3_column_count(self.stmt) }; let n = self.column_count;
let mut cols = Vec::with_capacity(n as usize); let mut cols = Vec::with_capacity(n as usize);
for i in 0..n { for i in 0..n {
let slice = unsafe { let slice = unsafe {
@ -612,23 +714,26 @@ impl<'conn> SqliteStatement<'conn> {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails, the executed statement returns rows (in
/// which case `query` should be used instead), or the underling SQLite call fails.
pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<c_int> { pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<c_int> {
self.reset_if_needed();
unsafe { unsafe {
assert!(params.len() as c_int == ffi::sqlite3_bind_parameter_count(self.stmt), try!(self.bind_parameters(params));
"incorrect number of parameters to execute(): expected {}, got {}",
ffi::sqlite3_bind_parameter_count(self.stmt),
params.len());
for (i, p) in params.iter().enumerate() {
try!(self.conn.decode_result(p.bind_parameter(self.stmt, (i + 1) as c_int)));
}
self.needs_reset = true;
let r = ffi::sqlite3_step(self.stmt); let r = ffi::sqlite3_step(self.stmt);
ffi::sqlite3_reset(self.stmt);
match r { match r {
ffi::SQLITE_DONE => Ok(self.conn.changes()), ffi::SQLITE_DONE => {
if self.column_count != 0 {
Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Unexpected column count - did you mean to call query?".to_string() })
} else {
Ok(self.conn.changes())
}
},
ffi::SQLITE_ROW => Err(SqliteError{ code: r, ffi::SQLITE_ROW => Err(SqliteError{ code: r,
message: "Unexpected row result - did you mean to call query?".to_string() }), message: "Unexpected row result - did you mean to call query?".to_string() }),
_ => Err(self.conn.decode_result(r).unwrap_err()), _ => Err(self.conn.decode_result(r).unwrap_err()),
@ -655,6 +760,10 @@ impl<'conn> SqliteStatement<'conn> {
/// Ok(names) /// Ok(names)
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> SqliteResult<SqliteRows<'a>> { pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> SqliteResult<SqliteRows<'a>> {
self.reset_if_needed(); self.reset_if_needed();
@ -662,6 +771,7 @@ impl<'conn> SqliteStatement<'conn> {
try!(self.bind_parameters(params)); try!(self.bind_parameters(params));
} }
self.needs_reset = true;
Ok(SqliteRows::new(self)) Ok(SqliteRows::new(self))
} }
@ -670,10 +780,13 @@ impl<'conn> SqliteStatement<'conn> {
/// ///
/// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility
/// for accessing stale rows. /// for accessing stale rows.
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F)
-> SqliteResult<MappedRows<'a, F>> -> SqliteResult<MappedRows<'a, F>>
where T: 'static, where F: FnMut(&SqliteRow) -> T {
F: FnMut(SqliteRow) -> T {
let row_iter = try!(self.query(params)); let row_iter = try!(self.query(params));
Ok(MappedRows{ Ok(MappedRows{
@ -682,10 +795,36 @@ impl<'conn> SqliteStatement<'conn> {
}) })
} }
/// Executes the prepared statement and maps a function over the resulting
/// rows, where the function returns a `Result` with `Error` type implementing
/// `std::convert::From<SqliteError>` (so errors can be unified).
///
/// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility
/// for accessing stale rows.
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, F>(&'a mut self, params: &[&ToSql], f: F)
-> SqliteResult<AndThenRows<'a, F>>
where E: convert::From<SqliteError>,
F: FnMut(&SqliteRow) -> Result<T, E> {
let row_iter = try!(self.query(params));
Ok(AndThenRows{
rows: row_iter,
map: f,
})
}
/// Consumes the statement. /// Consumes the statement.
/// ///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors /// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors
/// that occur. /// that occur.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn finalize(mut self) -> SqliteResult<()> { pub fn finalize(mut self) -> SqliteResult<()> {
self.finalize_() self.finalize_()
} }
@ -700,8 +839,6 @@ impl<'conn> SqliteStatement<'conn> {
try!(self.conn.decode_result(p.bind_parameter(self.stmt, (i + 1) as c_int))); try!(self.conn.decode_result(p.bind_parameter(self.stmt, (i + 1) as c_int)));
} }
self.needs_reset = true;
Ok(()) Ok(())
} }
@ -721,7 +858,15 @@ impl<'conn> SqliteStatement<'conn> {
impl<'conn> fmt::Debug for SqliteStatement<'conn> { impl<'conn> fmt::Debug for SqliteStatement<'conn> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Statement( conn: {:?}, stmt: {:?} )", self.conn, self.stmt) let sql = unsafe {
let c_slice = CStr::from_ptr(ffi::sqlite3_sql(self.stmt)).to_bytes();
str::from_utf8(c_slice)
};
f.debug_struct("SqliteStatement")
.field("conn", self.conn)
.field("stmt", &self.stmt)
.field("sql", &sql)
.finish()
} }
} }
@ -739,12 +884,30 @@ pub struct MappedRows<'stmt, F> {
} }
impl<'stmt, T, F> Iterator for MappedRows<'stmt, F> impl<'stmt, T, F> Iterator for MappedRows<'stmt, F>
where T: 'static, where F: FnMut(&SqliteRow) -> T {
F: FnMut(SqliteRow) -> T {
type Item = SqliteResult<T>; type Item = SqliteResult<T>;
fn next(&mut self) -> Option<SqliteResult<T>> { fn next(&mut self) -> Option<SqliteResult<T>> {
self.rows.next().map(|row_result| row_result.map(|row| (self.map)(row))) self.rows.next().map(|row_result| row_result.map(|row| (self.map)(&row)))
}
}
/// An iterator over the mapped resulting rows of a query, with an Error type
/// unifying with SqliteError.
pub struct AndThenRows<'stmt, F> {
rows: SqliteRows<'stmt>,
map: F,
}
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<SqliteError>,
F: FnMut(&SqliteRow) -> Result<T, E> {
type Item = Result<T, E>;
fn next(&mut self) -> Option<Self::Item> {
self.rows.next().map(|row_result| row_result
.map_err(E::from)
.and_then(|row| (self.map)(&row)))
} }
} }
@ -858,7 +1021,7 @@ impl<'stmt> SqliteRow<'stmt> {
/// Panics if `idx` is outside the range of columns in the returned query or if this row /// Panics if `idx` is outside the range of columns in the returned query or if this row
/// is stale. /// is stale.
pub fn get<T: FromSql>(&self, idx: c_int) -> T { pub fn get<T: FromSql>(&self, idx: c_int) -> T {
self.get_opt(idx).unwrap() self.get_checked(idx).unwrap()
} }
/// Get the value of a particular column of the result row. /// Get the value of a particular column of the result row.
@ -868,15 +1031,21 @@ impl<'stmt> SqliteRow<'stmt> {
/// Returns a `SQLITE_MISMATCH`-coded `SqliteError` if the underlying SQLite column /// Returns a `SQLITE_MISMATCH`-coded `SqliteError` if the underlying SQLite column
/// type is not a valid type as a source for `T`. /// type is not a valid type as a source for `T`.
/// ///
/// Panics if `idx` is outside the range of columns in the returned query or if this row /// Returns a `SQLITE_MISUSE`-coded `SqliteError` if `idx` is outside the valid column range
/// is stale. /// for this row or if this row is stale.
pub fn get_checked<T: FromSql>(&self, idx: c_int) -> SqliteResult<T> { pub fn get_checked<T: FromSql>(&self, idx: c_int) -> SqliteResult<T> {
let valid_column_type = unsafe { if self.row_idx != self.current_row.get() {
T::column_has_valid_sqlite_type(self.stmt.stmt, idx) return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
}; message: "Cannot get values from a row after advancing to next row".to_string() });
}
unsafe {
if idx < 0 || idx >= self.stmt.column_count {
return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_string() });
}
if valid_column_type { if T::column_has_valid_sqlite_type(self.stmt.stmt, idx) {
Ok(self.get(idx)) FromSql::column_result(self.stmt.stmt, idx)
} else { } else {
Err(SqliteError{ Err(SqliteError{
code: ffi::SQLITE_MISMATCH, code: ffi::SQLITE_MISMATCH,
@ -884,25 +1053,6 @@ impl<'stmt> SqliteRow<'stmt> {
}) })
} }
} }
/// Attempt to get the value of a particular column of the result row.
///
/// ## Failure
///
/// Returns a `SQLITE_MISUSE`-coded `SqliteError` if `idx` is outside the valid column range
/// for this row or if this row is stale.
pub fn get_opt<T: FromSql>(&self, idx: c_int) -> SqliteResult<T> {
if self.row_idx != self.current_row.get() {
return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Cannot get values from a row after advancing to next row".to_string() });
}
unsafe {
if idx < 0 || idx >= ffi::sqlite3_column_count(self.stmt.stmt) {
return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_string() });
}
FromSql::column_result(self.stmt.stmt, idx)
}
} }
} }
@ -910,8 +1060,10 @@ impl<'stmt> SqliteRow<'stmt> {
mod test { mod test {
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
extern crate tempdir; extern crate tempdir;
use super::*; pub use super::*;
use self::tempdir::TempDir; use self::tempdir::TempDir;
pub use std::error::Error as StdError;
pub use std::fmt;
// this function is never called, but is still type checked; in // this function is never called, but is still type checked; in
// particular, calls with specific instantiations will require // particular, calls with specific instantiations will require
@ -921,7 +1073,7 @@ mod test {
ensure_send::<SqliteConnection>(); ensure_send::<SqliteConnection>();
} }
fn checked_memory_handle() -> SqliteConnection { pub fn checked_memory_handle() -> SqliteConnection {
SqliteConnection::open_in_memory().unwrap() SqliteConnection::open_in_memory().unwrap()
} }
@ -995,6 +1147,14 @@ mod test {
assert_eq!(3i32, db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap()); assert_eq!(3i32, db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
} }
#[test]
fn test_execute_select() {
let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?", &[&1i32]).unwrap_err();
assert!(err.code == ffi::SQLITE_MISUSE);
assert!(err.message == "Unexpected column count - did you mean to call query?");
}
#[test] #[test]
fn test_prepare_column_names() { fn test_prepare_column_names() {
let db = checked_memory_handle(); let db = checked_memory_handle();
@ -1123,7 +1283,7 @@ mod test {
assert_eq!(2i32, second.get(0)); assert_eq!(2i32, second.get(0));
let result = first.get_opt::<i32>(0); let result = first.get_checked::<i32>(0);
assert!(result.unwrap_err().message.contains("advancing to next row")); assert!(result.unwrap_err().message.contains("advancing to next row"));
} }
@ -1141,4 +1301,217 @@ mod test {
} }
assert_eq!(db.last_insert_rowid(), 10); assert_eq!(db.last_insert_rowid(), 10);
} }
#[test]
fn test_statement_debugging() {
let db = checked_memory_handle();
let query = "SELECT 12345";
let stmt = db.prepare(query).unwrap();
assert!(format!("{:?}", stmt).contains(query));
}
mod query_and_then_tests {
extern crate libsqlite3_sys as ffi;
use super::*;
#[derive(Debug, PartialEq)]
enum CustomError {
SomeError,
Sqlite(SqliteError),
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
CustomError::SomeError => write!(f, "{}", self.description()),
CustomError::Sqlite(ref se) => write!(f, "{}: {}", self.description(), se),
}
}
}
impl StdError for CustomError {
fn description(&self) -> &str { "my custom error" }
fn cause(&self) -> Option<&StdError> {
match *self {
CustomError::SomeError => None,
CustomError::Sqlite(ref se) => Some(se),
}
}
}
impl From<SqliteError> for CustomError {
fn from(se: SqliteError) -> CustomError {
CustomError::Sqlite(se)
}
}
type CustomResult<T> = Result<T, CustomError>;
#[test]
fn test_query_and_then() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: SqliteResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(1))
.unwrap()
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!");
}
#[test]
fn test_query_and_then_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: SqliteResult<Vec<f64>> = query
.query_and_then(&[], |row| row.get_checked(1))
.unwrap()
.collect();
assert_eq!(bad_type, Err(SqliteError{
code: ffi::SQLITE_MISMATCH,
message: "Invalid column type".to_owned(),
}));
let bad_idx: SqliteResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(3))
.unwrap()
.collect();
assert_eq!(bad_idx, Err(SqliteError{
code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_owned(),
}));
}
#[test]
fn test_query_and_then_custom_error() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let results: CustomResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
assert_eq!(results.unwrap().concat(), "hello, world!");
}
#[test]
fn test_query_and_then_custom_error_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
INSERT INTO foo VALUES(3, \", \");
INSERT INTO foo VALUES(2, \"world\");
INSERT INTO foo VALUES(1, \"!\");
END;";
db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: CustomResult<Vec<f64>> = query
.query_and_then(&[], |row| row.get_checked(1).map_err(CustomError::Sqlite))
.unwrap()
.collect();
assert_eq!(bad_type, Err(CustomError::Sqlite(SqliteError{
code: ffi::SQLITE_MISMATCH,
message: "Invalid column type".to_owned(),
})));
let bad_idx: CustomResult<Vec<String>> = query
.query_and_then(&[], |row| row.get_checked(3).map_err(CustomError::Sqlite))
.unwrap()
.collect();
assert_eq!(bad_idx, Err(CustomError::Sqlite(SqliteError{
code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_owned(),
})));
let non_sqlite_err: CustomResult<Vec<String>> = query
.query_and_then(&[], |_| Err(CustomError::SomeError))
.unwrap()
.collect();
assert_eq!(non_sqlite_err, Err(CustomError::SomeError));
}
#[test]
fn test_query_row_and_then_custom_error() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
db.execute_batch(sql).unwrap();
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let results: CustomResult<String> = db
.query_row_and_then(query, &[], |row| row.get_checked(1).map_err(CustomError::Sqlite));
assert_eq!(results.unwrap(), "hello");
}
#[test]
fn test_query_row_and_then_custom_error_fails() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, \"hello\");
END;";
db.execute_batch(sql).unwrap();
let query = "SELECT x, y FROM foo ORDER BY x DESC";
let bad_type: CustomResult<f64> = db
.query_row_and_then(query, &[], |row| row.get_checked(1).map_err(CustomError::Sqlite));
assert_eq!(bad_type, Err(CustomError::Sqlite(SqliteError{
code: ffi::SQLITE_MISMATCH,
message: "Invalid column type".to_owned(),
})));
let bad_idx: CustomResult<String> = db
.query_row_and_then(query, &[], |row| row.get_checked(3).map_err(CustomError::Sqlite));
assert_eq!(bad_idx, Err(CustomError::Sqlite(SqliteError{
code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_owned(),
})));
let non_sqlite_err: CustomResult<String> = db
.query_row_and_then(query, &[], |_| Err(CustomError::SomeError));
assert_eq!(non_sqlite_err, Err(CustomError::SomeError));
}
}
} }

162
src/trace.rs Normal file
View File

@ -0,0 +1,162 @@
//! Tracing and profiling functions. Error and warning log.
use libc::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr;
use std::str;
use std::time::Duration;
use super::ffi;
use {SqliteError, SqliteResult, SqliteConnection};
/// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons:
///
/// * The function is not threadsafe. No other SQLite calls may be made while
/// `config_log` is running, and multiple threads may not call `config_log`
/// simultaneously.
/// * The provided `callback` itself function has two requirements:
/// * It must not invoke any SQLite calls.
/// * 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)>) -> SqliteResult<()> {
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) };
if let Ok(s) = str::from_utf8(c_slice) {
callback(err, s);
}
}
let rc = match callback {
Some(f) => {
let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, Some(log_callback), p_arg)
},
None => {
let nullptr: *mut c_void = ptr::null_mut();
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
}
};
if rc != ffi::SQLITE_OK {
return Err(SqliteError{ code: rc, message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string() });
}
Ok(())
}
/// Write a message into the error log established by `config_log`.
pub fn log(err_code: c_int, msg: &str) {
let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
unsafe {
ffi::sqlite3_log(err_code, msg.as_ptr());
}
}
impl SqliteConnection {
/// 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 values.
/// There can only be a single tracer defined for each database connection.
/// Setting a new tracer clears the old one.
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
extern "C" fn trace_callback (p_arg: *mut c_void, z_sql: *const c_char) {
let trace_fn: fn(&str) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) {
trace_fn(s);
}
}
let c = self.db.borrow_mut();
match trace_fn {
Some(f) => unsafe { ffi::sqlite3_trace(c.db(), Some(trace_callback), mem::transmute(f)); },
None => unsafe { ffi::sqlite3_trace(c.db(), None, ptr::null_mut()); },
}
}
/// 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 connection.
/// Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
extern "C" fn profile_callback(p_arg: *mut c_void, z_sql: *const c_char, nanoseconds: u64) {
let profile_fn: fn(&str, Duration) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) {
const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32);
profile_fn(s, duration);
}
}
let c = self.db.borrow_mut();
match profile_fn {
Some(f) => unsafe { ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f)) },
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
};
}
}
#[cfg(test)]
mod test {
use std::sync::Mutex;
use std::time::Duration;
use SqliteConnection;
#[test]
fn test_trace() {
lazy_static! {
static ref TRACED_STMTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
fn tracer(s: &str) {
let mut traced_stmts = TRACED_STMTS.lock().unwrap();
traced_stmts.push(s.to_owned());
}
let mut db = SqliteConnection::open_in_memory().unwrap();
db.trace(Some(tracer));
{
let _ = db.query_row("SELECT ?", &[&1i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"hello"], |_| {});
}
db.trace(None);
{
let _ = db.query_row("SELECT ?", &[&2i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"goodbye"], |_| {});
}
let traced_stmts = TRACED_STMTS.lock().unwrap();
assert_eq!(traced_stmts.len(), 2);
assert_eq!(traced_stmts[0], "SELECT 1");
assert_eq!(traced_stmts[1], "SELECT 'hello'");
}
#[test]
fn test_profile() {
lazy_static! {
static ref PROFILED: Mutex<Vec<(String, Duration)>> = Mutex::new(Vec::new());
}
fn profiler(s: &str, d: Duration) {
let mut profiled = PROFILED.lock().unwrap();
profiled.push((s.to_owned(), d));
}
let mut db = SqliteConnection::open_in_memory().unwrap();
db.profile(Some(profiler));
db.execute_batch("PRAGMA application_id = 1").unwrap();
db.profile(None);
db.execute_batch("PRAGMA application_id = 2").unwrap();
let profiled = PROFILED.lock().unwrap();
assert_eq!(profiled.len(), 1);
assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
}
}

View File

@ -74,7 +74,7 @@ pub trait ToSql {
} }
/// A trait for types that can be created from a SQLite value. /// A trait for types that can be created from a SQLite value.
pub trait FromSql { pub trait FromSql: Sized {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<Self>; unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> SqliteResult<Self>;
/// FromSql types can implement this method and use sqlite3_column_type to check that /// FromSql types can implement this method and use sqlite3_column_type to check that
@ -111,8 +111,12 @@ impl ToSql for bool {
impl<'a> ToSql for &'a str { impl<'a> ToSql for &'a str {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let length = self.len();
if length > ::std::i32::MAX as usize {
return ffi::SQLITE_TOOBIG;
}
match str_to_cstring(self) { match str_to_cstring(self) {
Ok(c_str) => ffi::sqlite3_bind_text(stmt, col, c_str.as_ptr(), -1, Ok(c_str) => ffi::sqlite3_bind_text(stmt, col, c_str.as_ptr(), length as c_int,
ffi::SQLITE_TRANSIENT()), ffi::SQLITE_TRANSIENT()),
Err(_) => ffi::SQLITE_MISUSE, Err(_) => ffi::SQLITE_MISUSE,
} }
@ -127,6 +131,9 @@ impl ToSql for String {
impl<'a> ToSql for &'a [u8] { impl<'a> ToSql for &'a [u8] {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
if self.len() > ::std::i32::MAX as usize {
return ffi::SQLITE_TOOBIG;
}
ffi::sqlite3_bind_blob( ffi::sqlite3_bind_blob(
stmt, col, mem::transmute(self.as_ptr()), self.len() as c_int, ffi::SQLITE_TRANSIENT()) stmt, col, mem::transmute(self.as_ptr()), self.len() as c_int, ffi::SQLITE_TRANSIENT())
} }

36
tests/config_log.rs Normal file
View File

@ -0,0 +1,36 @@
//! This file contains unit tests for rusqlite::trace::config_log. This function affects
//! SQLite process-wide and so is not safe to run as a normal #[test] in the library.
#[macro_use] extern crate lazy_static;
extern crate libc;
extern crate rusqlite;
#[cfg(feature = "trace")]
fn main() {
use libc::c_int;
use std::sync::Mutex;
lazy_static! {
static ref LOGS_RECEIVED: Mutex<Vec<(c_int, String)>> = Mutex::new(Vec::new());
}
fn log_handler(err: c_int, message: &str) {
let mut logs_received = LOGS_RECEIVED.lock().unwrap();
logs_received.push((err, message.to_owned()));
}
use rusqlite::trace;
unsafe { trace::config_log(Some(log_handler)) }.unwrap();
trace::log(10, "First message from rusqlite");
unsafe { trace::config_log(None) }.unwrap();
trace::log(11, "Second message from rusqlite");
let logs_received = LOGS_RECEIVED.lock().unwrap();
assert_eq!(logs_received.len(), 1);
assert_eq!(logs_received[0].0, 10);
assert_eq!(logs_received[0].1, "First message from rusqlite");
}
#[cfg(not(feature = "trace"))]
fn main() {}