Merge branch 'master' into gwenn-convenient

This commit is contained in:
John Gallagher 2016-05-16 11:02:56 -05:00
commit 504b16dc98
19 changed files with 710 additions and 400 deletions

View File

@ -13,4 +13,6 @@ script:
- cargo test --features load_extension
- cargo test --features trace
- cargo test --features functions
- cargo test --features "backup blob functions load_extension trace"
- cargo test --features chrono
- cargo test --features serde_json
- cargo test --features "backup blob chrono functions load_extension serde_json trace"

View File

@ -1,5 +1,5 @@
rusqlite contributors (sorted alphabetically)
=============================================
rusqlite contributors
=====================
* [John Gallagher](https://github.com/jgallagher)
* [Marcus Klaas de Vries](https://github.com/marcusklaas)
@ -13,3 +13,4 @@ rusqlite contributors (sorted alphabetically)
* [Andrew Straw](https://github.com/astraw)
* [Ronald Kinard](https://github.com/Furyhunter)
* [maciejkula](https://github.com/maciejkula)
* [Xidorn Quan](https://github.com/upsuper)

View File

@ -13,7 +13,7 @@ license = "MIT"
name = "rusqlite"
[features]
load_extension = ["libsqlite3-sys/load_extension"]
load_extension = []
backup = []
blob = []
functions = []
@ -23,6 +23,9 @@ trace = []
time = "~0.1.0"
bitflags = "~0.1"
libc = "~0.2"
clippy = {version = "~0.0.58", optional = true}
chrono = { version = "~0.2", optional = true }
serde_json = { version = "0.6", optional = true }
[dev-dependencies]
tempdir = "~0.3.4"

View File

@ -1,9 +1,19 @@
# Version UPCOMING (...)
* Adds support for serializing types from the `serde_json` crate. Requires the `serde_json` feature.
* Adds support for serializing types from the `chrono` crate. Requires the `chrono` feature.
* Removes `load_extension` feature from `libsqlite3-sys`. `load_extension` is still available
on rusqlite itself.
* Fixes crash on nightly Rust when using the `trace` feature.
* Adds optional `clippy` feature and addresses issues it found.
* Adds `column_count()` method to `Statement` and `Row`.
* Adds `types::Value` for dynamic column types.
* Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature).
* Introduces a `RowIndex` trait allowing columns to be fetched via index (as before) or name (new).
* Introduces `ZeroBlob` type under the `blob` module/feature exposing SQLite's zeroblob API.
* Adds CI testing for Windows via AppVeyor.
* Fixes a warning building libsqlite3-sys under Rust 1.6.
* Adds an unsafe `handle()` method to `Connection`. Please file an issue if you actually use it.
# Version 0.6.0 (2015-12-17)

View File

@ -1,6 +1,7 @@
# Rusqlite
[![Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
[![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite)
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
@ -72,45 +73,6 @@ features](http://doc.crates.io/manifest.html#the-features-section). They are:
* [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html)
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs.
### Design of Rows and Row
To retrieve the result rows from a query, SQLite requires you to call
[sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only
retrieve the values of the "current" row. From the Rust point of view, this means that each row
is only valid until the next row is fetched. [rust-sqlite3](https://github.com/dckc/rust-sqlite3)
solves this the correct way with lifetimes. However, this means that the result rows do not
satisfy the [Iterator](http://doc.rust-lang.org/std/iter/trait.Iterator.html) trait, which means
you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like `map`
and `filter`.
Instead, Rusqlite's `Rows` handle does conform to `Iterator`. It ensures safety by
performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and
will panic if you do so. A specific example that will panic:
```rust
fn bad_function_will_panic(conn: &Connection) -> Result<i64> {
let mut stmt = try!(conn.prepare("SELECT id FROM my_table"));
let mut rows = try!(stmt.query(&[]));
let row0 = try!(rows.next().unwrap());
// row 0 is valid now...
let row1 = try!(rows.next().unwrap());
// row 0 is now STALE, and row 1 is valid
let my_id = row0.get(0); // WILL PANIC because row 0 is stale
Ok(my_id)
}
```
There are other, less obvious things that may result in a panic as well, such as calling
`collect()` on a `Rows` and then trying to use the collected rows.
Strongly consider using the method `query_map()` instead, if you can.
`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
access stale rows.
## Author
John Gallagher, johnkgallagher@gmail.com

View File

@ -16,7 +16,7 @@ build: false
test_script:
- cargo test --lib --verbose
- cargo test --lib --features "backup blob functions load_extension trace"
- cargo test --lib --features "backup blob chrono functions load_extension serde_json trace"
cache:
- C:\Users\appveyor\.cargo

View File

@ -8,9 +8,6 @@ license = "MIT"
links = "sqlite3"
build = "build.rs"
[features]
load_extension = []
[build-dependencies]
pkg-config = "~0.3"

View File

@ -171,9 +171,7 @@ impl<'a, 'b> Backup<'a, 'b> {
///
/// Will return `Err` if the underlying `sqlite3_backup_init` call returns
/// `NULL`.
pub fn new(from: &'a Connection,
to: &'b mut Connection)
-> Result<Backup<'a, 'b>> {
pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
}

View File

@ -352,7 +352,8 @@ mod test {
{
// ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false).unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap();
let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
assert_eq!(b"0123456701", &bytes);
@ -369,7 +370,8 @@ mod test {
{
// ... but it should've written the first 10 bytes
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false).unwrap();
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false)
.unwrap();
let mut bytes = [0u8; 10];
assert_eq!(10, blob.read(&mut bytes[..]).unwrap());
assert_eq!(b"aaaaaaaaaa", &bytes);

View File

@ -56,6 +56,9 @@ pub enum Error {
/// that column cannot be converted to the requested Rust type.
InvalidColumnType,
/// Error when a query that was expected to insert one row did not insert any or insert many.
QueryInsertedRows(c_int),
/// Error returned by `functions::Context::get` when the function argument cannot be converted
/// to the requested type.
#[cfg(feature = "functions")]
@ -66,9 +69,6 @@ pub enum Error {
#[cfg(feature = "functions")]
#[allow(dead_code)]
UserFunctionError(Box<error::Error + Send + Sync>),
/// Error when a query that was expected to insert one row did not insert any or insert many.
QueryInsertedRows(c_int),
}
impl From<str::Utf8Error> for Error {
@ -85,81 +85,90 @@ impl From<::std::ffi::NulError> for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::SqliteFailure(ref err, None) => err.fmt(f),
&Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
&Error::SqliteSingleThreadedMode => write!(f, "SQLite was compiled or configured for single-threaded use only"),
&Error::FromSqlConversionFailure(ref err) => err.fmt(f),
&Error::Utf8Error(ref err) => err.fmt(f),
&Error::NulError(ref err) => err.fmt(f),
&Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
&Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
&Error::ExecuteReturnedResults => write!(f, "Execute returned results - did you mean to call query?"),
&Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
&Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"),
&Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
&Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
&Error::InvalidColumnType => write!(f, "Invalid column type"),
match *self {
Error::SqliteFailure(ref err, None) => err.fmt(f),
Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
Error::SqliteSingleThreadedMode => {
write!(f,
"SQLite was compiled or configured for single-threaded use only")
}
Error::FromSqlConversionFailure(ref err) => err.fmt(f),
Error::Utf8Error(ref err) => err.fmt(f),
Error::NulError(ref err) => err.fmt(f),
Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
Error::ExecuteReturnedResults => {
write!(f, "Execute returned results - did you mean to call query?")
}
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType => write!(f, "Invalid column type"),
Error::QueryInsertedRows(i) => write!(f, "Query inserted {} rows", i),
#[cfg(feature = "functions")]
&Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"),
Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"),
#[cfg(feature = "functions")]
&Error::UserFunctionError(ref err) => err.fmt(f),
&Error::QueryInsertedRows(i) => write!(f, "Query inserted {} rows", i),
Error::UserFunctionError(ref err) => err.fmt(f),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match self {
&Error::SqliteFailure(ref err, None) => err.description(),
&Error::SqliteFailure(_, Some(ref s)) => s,
&Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only",
&Error::FromSqlConversionFailure(ref err) => err.description(),
&Error::Utf8Error(ref err) => err.description(),
&Error::InvalidParameterName(_) => "invalid parameter name",
&Error::NulError(ref err) => err.description(),
&Error::InvalidPath(_) => "invalid path",
&Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?",
&Error::QueryReturnedNoRows => "query returned no rows",
&Error::GetFromStaleRow => "attempted to get a value from a stale row",
&Error::InvalidColumnIndex(_) => "invalid column index",
&Error::InvalidColumnName(_) => "invalid column name",
&Error::InvalidColumnType => "invalid column type",
match *self {
Error::SqliteFailure(ref err, None) => err.description(),
Error::SqliteFailure(_, Some(ref s)) => s,
Error::SqliteSingleThreadedMode => {
"SQLite was compiled or configured for single-threaded use only"
}
Error::FromSqlConversionFailure(ref err) => err.description(),
Error::Utf8Error(ref err) => err.description(),
Error::InvalidParameterName(_) => "invalid parameter name",
Error::NulError(ref err) => err.description(),
Error::InvalidPath(_) => "invalid path",
Error::ExecuteReturnedResults => {
"execute returned results - did you mean to call query?"
}
Error::QueryReturnedNoRows => "query returned no rows",
Error::GetFromStaleRow => "attempted to get a value from a stale row",
Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType => "invalid column type",
Error::QueryInsertedRows(_) => "query inserted zero or more than one row",
#[cfg(feature = "functions")]
&Error::InvalidFunctionParameterType => "invalid function parameter type",
Error::InvalidFunctionParameterType => "invalid function parameter type",
#[cfg(feature = "functions")]
&Error::UserFunctionError(ref err) => err.description(),
&Error::QueryInsertedRows(_) => "query inserted zero or more than one row",
Error::UserFunctionError(ref err) => err.description(),
}
}
#[cfg_attr(feature="clippy", allow(match_same_arms))]
fn cause(&self) -> Option<&error::Error> {
match self {
&Error::SqliteFailure(ref err, _) => Some(err),
&Error::SqliteSingleThreadedMode => None,
&Error::FromSqlConversionFailure(ref err) => Some(&**err),
&Error::Utf8Error(ref err) => Some(err),
&Error::NulError(ref err) => Some(err),
&Error::InvalidParameterName(_) => None,
&Error::InvalidPath(_) => None,
&Error::ExecuteReturnedResults => None,
&Error::QueryReturnedNoRows => None,
&Error::GetFromStaleRow => None,
&Error::InvalidColumnIndex(_) => None,
&Error::InvalidColumnName(_) => None,
&Error::InvalidColumnType => None,
match *self {
Error::SqliteFailure(ref err, _) => Some(err),
Error::FromSqlConversionFailure(ref err) => Some(&**err),
Error::Utf8Error(ref err) => Some(err),
Error::NulError(ref err) => Some(err),
Error::SqliteSingleThreadedMode |
Error::InvalidParameterName(_) |
Error::ExecuteReturnedResults |
Error::QueryReturnedNoRows |
Error::GetFromStaleRow |
Error::InvalidColumnIndex(_) |
Error::InvalidColumnName(_) |
Error::InvalidColumnType |
Error::InvalidPath(_) => None,
Error::QueryInsertedRows(_) => None,
#[cfg(feature = "functions")]
&Error::InvalidFunctionParameterType => None,
#[cfg(feature = "functions")]
&Error::UserFunctionError(ref err) => Some(&**err),
Error::InvalidFunctionParameterType => None,
&Error::QueryInsertedRows(_) => None,
#[cfg(feature = "functions")]
Error::UserFunctionError(ref err) => Some(&**err),
}
}
}

View File

@ -88,9 +88,10 @@ raw_to_impl!(c_double, sqlite3_result_double);
impl<'a> ToResult for bool {
unsafe fn set_result(&self, ctx: *mut sqlite3_context) {
match *self {
true => ffi::sqlite3_result_int(ctx, 1),
_ => ffi::sqlite3_result_int(ctx, 0),
if *self {
ffi::sqlite3_result_int(ctx, 1)
} else {
ffi::sqlite3_result_int(ctx, 0)
}
}
}
@ -214,7 +215,7 @@ impl FromValue for String {
unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<String> {
let c_text = ffi::sqlite3_value_text(v);
if c_text.is_null() {
Ok("".to_string())
Ok("".to_owned())
} else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = try!(str::from_utf8(c_slice));
@ -250,7 +251,7 @@ impl<T: FromValue> FromValue for Option<T> {
if sqlite3_value_type(v) == ffi::SQLITE_NULL {
Ok(None)
} else {
FromValue::parameter_value(v).map(|t| Some(t))
FromValue::parameter_value(v).map(Some)
}
}
@ -274,6 +275,10 @@ impl<'a> Context<'a> {
pub fn len(&self) -> usize {
self.args.len()
}
/// Returns `true` when there is no argument.
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
/// Returns the `idx`th argument as a `T`.
///
@ -302,7 +307,7 @@ impl<'a> Context<'a> {
ffi::sqlite3_set_auxdata(self.ctx,
arg,
mem::transmute(boxed),
Some(mem::transmute(free_boxed_value::<T>)))
Some(free_boxed_value::<T>))
};
}
@ -475,7 +480,7 @@ impl InnerConnection {
Some(call_boxed_closure::<F, T>),
None,
None,
Some(mem::transmute(free_boxed_value::<F>)))
Some(free_boxed_value::<F>))
};
self.decode_result(r)
}
@ -592,7 +597,7 @@ impl InnerConnection {
None,
Some(call_boxed_step::<A, D, T>),
Some(call_boxed_final::<A, D, T>),
Some(mem::transmute(free_boxed_value::<D>)))
Some(free_boxed_value::<D>))
};
self.decode_result(r)
}
@ -621,6 +626,7 @@ mod test {
use std::collections::HashMap;
use libc::c_double;
use self::regex::Regex;
use std::f64::EPSILON;
use {Connection, Error, Result};
use functions::{Aggregate, Context};
@ -637,7 +643,7 @@ mod test {
db.create_scalar_function("half", 1, true, half).unwrap();
let result: Result<f64> = db.query_row("SELECT half(6)", &[], |r| r.get(0));
assert_eq!(3f64, result.unwrap());
assert!((3f64 - result.unwrap()).abs() < EPSILON);
}
#[test]
@ -645,7 +651,7 @@ mod test {
let db = Connection::open_in_memory().unwrap();
db.create_scalar_function("half", 1, true, half).unwrap();
let result: Result<f64> = db.query_row("SELECT half(6)", &[], |r| r.get(0));
assert_eq!(3f64, result.unwrap());
assert!((3f64 - result.unwrap()).abs() < EPSILON);
db.remove_function("half", 1).unwrap();
let result: Result<f64> = db.query_row("SELECT half(6)", &[], |r| r.get(0));

View File

@ -50,6 +50,9 @@
//! }
//! }
//! ```
#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
extern crate libc;
extern crate libsqlite3_sys as ffi;
#[macro_use]
@ -84,12 +87,12 @@ pub mod types;
mod transaction;
mod named_params;
mod error;
mod convenient;
#[cfg(feature = "load_extension")]mod load_extension_guard;
#[cfg(feature = "trace")]pub mod trace;
#[cfg(feature = "backup")]pub mod backup;
#[cfg(feature = "functions")] pub mod functions;
#[cfg(feature = "blob")] pub mod blob;
mod convenient;
#[cfg(feature = "functions")]pub mod functions;
#[cfg(feature = "blob")]pub mod blob;
/// Old name for `Result`. `SqliteResult` is deprecated.
pub type SqliteResult<T> = Result<T>;
@ -99,8 +102,7 @@ pub type Result<T> = result::Result<T, Error>;
unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
let c_slice = CStr::from_ptr(errmsg).to_bytes();
let utf8_str = str::from_utf8(c_slice);
utf8_str.unwrap_or("Invalid string encoding").to_string()
String::from_utf8_lossy(c_slice).into_owned()
}
fn str_to_cstring(s: &str) -> Result<CString> {
@ -128,9 +130,9 @@ pub enum DatabaseName<'a> {
// impl to avoid dead code warnings.
#[cfg(any(feature = "backup", feature = "blob"))]
impl<'a> DatabaseName<'a> {
fn to_cstring(self) -> Result<CString> {
fn to_cstring(&self) -> Result<CString> {
use self::DatabaseName::{Main, Temp, Attached};
match self {
match *self {
Main => str_to_cstring("main"),
Temp => str_to_cstring("temp"),
Attached(s) => str_to_cstring(s),
@ -183,17 +185,15 @@ impl Connection {
///
/// 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: OpenFlags)
-> Result<Connection> {
let c_path = try!(path_to_cstring(path.as_ref()));
InnerConnection::open_with_flags(&c_path, flags).map(|db| {
Connection {
db: RefCell::new(db),
path: Some(path.as_ref().to_path_buf()),
}
})
}
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: OpenFlags) -> Result<Connection> {
let c_path = try!(path_to_cstring(path.as_ref()));
InnerConnection::open_with_flags(&c_path, flags).map(|db| {
Connection {
db: RefCell::new(db),
path: Some(path.as_ref().to_path_buf()),
}
})
}
/// Open a new connection to an in-memory SQLite database.
///
@ -237,7 +237,7 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction<'a>(&'a self) -> Result<Transaction<'a>> {
pub fn transaction(&self) -> Result<Transaction> {
Transaction::new(self, TransactionBehavior::Deferred)
}
@ -248,11 +248,9 @@ impl Connection {
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior<'a>(&'a self,
behavior: TransactionBehavior)
-> Result<Transaction<'a>> {
Transaction::new(self, behavior)
}
pub fn transaction_with_behavior(&self, behavior: TransactionBehavior) -> Result<Transaction> {
Transaction::new(self, behavior)
}
/// Convenience method to run multiple SQL statements (that cannot take any parameters).
///
@ -360,7 +358,11 @@ impl Connection {
///
/// 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::Result<T, E>
pub fn query_row_and_then<T, E, F>(&self,
sql: &str,
params: &[&ToSql],
f: F)
-> result::Result<T, E>
where F: FnOnce(Row) -> result::Result<T, E>,
E: convert::From<Error>
{
@ -391,9 +393,9 @@ impl Connection {
/// does exactly the same thing.
pub fn query_row_safe<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T>
where F: FnOnce(Row) -> T
{
self.query_row(sql, params, f)
}
{
self.query_row(sql, params, f)
}
/// Prepare a SQL statement for execution.
///
@ -491,9 +493,21 @@ impl Connection {
pub fn load_extension<P: AsRef<Path>>(&self,
dylib_path: P,
entry_point: Option<&str>)
-> Result<()> {
self.db.borrow_mut().load_extension(dylib_path.as_ref(), entry_point)
}
-> Result<()> {
self.db.borrow_mut().load_extension(dylib_path.as_ref(), entry_point)
}
/// Get access to the underlying SQLite database connection handle.
///
/// # Warning
///
/// You should not need to use this function. If you do need to, please [open an issue
/// on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe
/// your use case. This function is unsafe because it gives you raw access to the SQLite
/// connection, and what you do with it could impact the safety of this `Connection`.
pub unsafe fn handle(&self) -> *mut ffi::Struct_sqlite3 {
self.db.borrow().db()
}
fn decode_result(&self, code: c_int) -> Result<()> {
self.db.borrow_mut().decode_result(code)
@ -507,8 +521,8 @@ impl Connection {
impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Connection")
.field("path", &self.path)
.finish()
.field("path", &self.path)
.finish()
}
}
@ -543,60 +557,54 @@ impl Default for OpenFlags {
}
impl InnerConnection {
fn open_with_flags(c_path: &CString,
flags: OpenFlags)
-> Result<InnerConnection> {
unsafe {
// Before opening the database, we need to check that SQLite hasn't been
// compiled or configured to be in single-threaded mode. If it has, we're
// exposing a very unsafe API to Rust, so refuse to open connections at all.
// Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only
// returns how SQLite was _compiled_; there is no public API to check whether
// someone called sqlite3_config() to set single-threaded mode. We can cheat
// by trying to allocate a mutex, though; in single-threaded mode due to
// compilation settings, the magic value 8 is returned (see the definition of
// sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h);
// in single-threaded mode due to sqlite3_config(), the magic value 8 is also
// returned (see the definition of noopMutexAlloc at
// https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c).
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
let is_singlethreaded = if mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC {
true
fn open_with_flags(c_path: &CString, flags: OpenFlags) -> Result<InnerConnection> {
unsafe {
// Before opening the database, we need to check that SQLite hasn't been
// compiled or configured to be in single-threaded mode. If it has, we're
// exposing a very unsafe API to Rust, so refuse to open connections at all.
// Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only
// returns how SQLite was _compiled_; there is no public API to check whether
// someone called sqlite3_config() to set single-threaded mode. We can cheat
// by trying to allocate a mutex, though; in single-threaded mode due to
// compilation settings, the magic value 8 is returned (see the definition of
// sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h);
// in single-threaded mode due to sqlite3_config(), the magic value 8 is also
// returned (see the definition of noopMutexAlloc at
// https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c).
const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
ffi::sqlite3_mutex_free(mutex_ptr);
if is_singlethreaded {
return Err(Error::SqliteSingleThreadedMode);
}
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK {
let e = if db.is_null() {
error_from_sqlite_code(r, None)
} else {
false
};
ffi::sqlite3_mutex_free(mutex_ptr);
if is_singlethreaded {
return Err(Error::SqliteSingleThreadedMode);
}
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK {
let e = if db.is_null() {
error_from_sqlite_code(r, None)
} else {
let e = error_from_handle(db, r);
ffi::sqlite3_close(db);
e
};
return Err(e);
}
let r = ffi::sqlite3_busy_timeout(db, 5000);
if r != ffi::SQLITE_OK {
let e = error_from_handle(db, r);
ffi::sqlite3_close(db);
return Err(e);
}
e
};
// attempt to turn on extended results code; don't fail if we can't.
ffi::sqlite3_extended_result_codes(db, 1);
Ok(InnerConnection { db: db })
return Err(e);
}
let r = ffi::sqlite3_busy_timeout(db, 5000);
if r != ffi::SQLITE_OK {
let e = error_from_handle(db, r);
ffi::sqlite3_close(db);
return Err(e);
}
// attempt to turn on extended results code; don't fail if we can't.
ffi::sqlite3_extended_result_codes(db, 1);
Ok(InnerConnection { db: db })
}
}
fn db(&self) -> *mut ffi::Struct_sqlite3 {
self.db
@ -622,10 +630,10 @@ impl InnerConnection {
let c_sql = try!(str_to_cstring(sql));
unsafe {
let r = ffi::sqlite3_exec(self.db(),
c_sql.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut());
c_sql.as_ptr(),
None,
ptr::null_mut(),
ptr::null_mut());
self.decode_result(r)
}
}
@ -664,25 +672,22 @@ impl InnerConnection {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
fn prepare<'a>(&mut self,
conn: &'a Connection,
sql: &str)
-> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = try!(str_to_cstring(sql));
let r = unsafe {
let len_with_nul = (sql.len() + 1) as c_int;
ffi::sqlite3_prepare_v2(self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
ptr::null_mut())
};
self.decode_result(r).map(|_| Statement::new(conn, c_stmt))
fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = try!(str_to_cstring(sql));
let r = unsafe {
let len_with_nul = (sql.len() + 1) as c_int;
ffi::sqlite3_prepare_v2(self.db(),
c_sql.as_ptr(),
len_with_nul,
&mut c_stmt,
ptr::null_mut())
};
self.decode_result(r).map(|_| Statement::new(conn, c_stmt))
}
fn changes(&mut self) -> c_int {
unsafe { ffi::sqlite3_changes(self.db()) }
@ -786,12 +791,12 @@ impl<'conn> Statement<'conn> {
ffi::sqlite3_reset(self.stmt);
match r {
ffi::SQLITE_DONE => {
if self.column_count != 0 {
Err(Error::ExecuteReturnedResults)
} else {
if self.column_count == 0 {
Ok(self.conn.changes())
} else {
Err(Error::ExecuteReturnedResults)
}
},
}
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
_ => Err(self.conn.decode_result(r).unwrap_err()),
}
@ -840,19 +845,16 @@ impl<'conn> Statement<'conn> {
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, F>(&'a mut self,
params: &[&ToSql],
f: F)
-> Result<MappedRows<'a, F>>
pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result<MappedRows<'a, F>>
where F: FnMut(&Row) -> T
{
let row_iter = try!(self.query(params));
{
let row_iter = try!(self.query(params));
Ok(MappedRows {
rows: row_iter,
map: f,
})
}
Ok(MappedRows {
rows: row_iter,
map: f,
})
}
/// Executes the prepared statement and maps a function over the resulting
/// rows, where the function returns a `Result` with `Error` type implementing
@ -867,17 +869,17 @@ impl<'conn> Statement<'conn> {
pub fn query_and_then<'a, T, E, F>(&'a mut self,
params: &[&ToSql],
f: F)
-> Result<AndThenRows<'a, F>>
-> Result<AndThenRows<'a, F>>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
let row_iter = try!(self.query(params));
{
let row_iter = try!(self.query(params));
Ok(AndThenRows {
rows: row_iter,
map: f,
})
}
Ok(AndThenRows {
rows: row_iter,
map: f,
})
}
/// Consumes the statement.
///
@ -893,9 +895,9 @@ impl<'conn> Statement<'conn> {
unsafe fn bind_parameters(&mut self, params: &[&ToSql]) -> Result<()> {
assert!(params.len() as c_int == ffi::sqlite3_bind_parameter_count(self.stmt),
"incorrect number of parameters to query(): expected {}, got {}",
ffi::sqlite3_bind_parameter_count(self.stmt),
params.len());
"incorrect number of parameters to query(): 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)));
@ -927,10 +929,10 @@ impl<'conn> fmt::Debug for Statement<'conn> {
str::from_utf8(c_slice)
};
f.debug_struct("Statement")
.field("conn", self.conn)
.field("stmt", &self.stmt)
.field("sql", &sql)
.finish()
.field("conn", self.conn)
.field("stmt", &self.stmt)
.field("sql", &sql)
.finish()
}
}
@ -964,15 +966,15 @@ pub struct AndThenRows<'stmt, F> {
}
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
where E: convert::From<Error>,
F: FnMut(&Row) -> result::Result<T, E>
{
type Item = result::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))
.and_then(|row| (self.map)(&row))
})
}
}
@ -984,6 +986,9 @@ pub type SqliteRows<'stmt> = Rows<'stmt>;
///
/// ## Warning
///
/// Strongly consider using `query_map` or `query_and_then` instead of `query`; the former do not
/// suffer from the following problem.
///
/// Due to the way SQLite returns result rows of a query, it is not safe to attempt to get values
/// from a row after it has become stale (i.e., `next()` has been called again on the `Rows`
/// iterator). For example:
@ -995,7 +1000,7 @@ pub type SqliteRows<'stmt> = Rows<'stmt>;
/// let mut rows = try!(stmt.query(&[]));
///
/// let row0 = try!(rows.next().unwrap());
/// // row 0 is value now...
/// // row 0 is valid for now...
///
/// let row1 = try!(rows.next().unwrap());
/// // row 0 is now STALE, and row 1 is valid
@ -1009,12 +1014,6 @@ pub type SqliteRows<'stmt> = Rows<'stmt>;
/// (which would result in a collection of rows, only the last of which can safely be used) and
/// `min`/`max` (which could return a stale row unless the last row happened to be the min or max,
/// respectively).
///
/// This problem could be solved by changing the signature of `next` to tie the lifetime of the
/// returned row to the lifetime of (a mutable reference to) the result rows handle, but this would
/// no longer implement `Iterator`, and therefore you would lose access to the majority of
/// functions which are useful (such as support for `for ... in ...` looping, `map`, `filter`,
/// etc.).
pub struct Rows<'stmt> {
stmt: &'stmt Statement<'stmt>,
current_row: Rc<Cell<c_int>>,
@ -1171,9 +1170,9 @@ impl<'a> RowIndex for &'a str {
#[cfg(test)]
mod test {
extern crate libsqlite3_sys as ffi;
extern crate tempdir;
pub use super::*;
use ffi;
use self::tempdir::TempDir;
pub use std::error::Error as StdError;
pub use std::fmt;
@ -1222,12 +1221,11 @@ mod test {
#[test]
fn test_open_with_flags() {
for bad_flags in [OpenFlags::empty(),
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_READ_WRITE,
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_CREATE]
.iter() {
assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
}
for bad_flags in &[OpenFlags::empty(),
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_READ_WRITE,
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_CREATE] {
assert!(Connection::open_in_memory_with_flags(*bad_flags).is_err());
}
}
#[test]
@ -1403,7 +1401,7 @@ mod test {
assert_eq!(2i32, second.get(0));
match first.get_checked::<i32,i32>(0).unwrap_err() {
match first.get_checked::<i32, i32>(0).unwrap_err() {
Error::GetFromStaleRow => (),
err => panic!("Unexpected error {}", err),
}
@ -1451,7 +1449,7 @@ mod test {
if version >= 3007016 {
assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL)
}
},
}
err => panic!("Unexpected error {}", err),
}
}

View File

@ -37,11 +37,7 @@ impl Connection {
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row_named<T, F>(&self,
sql: &str,
params: &[(&str, &ToSql)],
f: F)
-> Result<T>
pub fn query_row_named<T, F>(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result<T>
where F: FnOnce(Row) -> T
{
let mut stmt = try!(self.prepare(sql));
@ -91,9 +87,7 @@ impl<'conn> Statement<'conn> {
/// which case `query` should be used instead), or the underling SQLite call fails.
pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> Result<c_int> {
try!(self.bind_parameters_named(params));
unsafe {
self.execute_()
}
unsafe { self.execute_() }
}
/// Execute the prepared statement with named parameter(s), returning an iterator over the
@ -118,9 +112,7 @@ impl<'conn> Statement<'conn> {
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_named<'a>(&'a mut self,
params: &[(&str, &ToSql)])
-> Result<Rows<'a>> {
pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result<Rows<'a>> {
self.reset_if_needed();
try!(self.bind_parameters_named(params));
@ -198,8 +190,10 @@ mod test {
let mut stmt = db.prepare("INSERT INTO test (x, y) VALUES (:x, :y)").unwrap();
stmt.execute_named(&[(":x", &"one")]).unwrap();
let result: Option<String> = db.query_row("SELECT y FROM test WHERE x = 'one'", &[],
|row| row.get(0)).unwrap();
let result: Option<String> = db.query_row("SELECT y FROM test WHERE x = 'one'",
&[],
|row| row.get(0))
.unwrap();
assert!(result.is_none());
}
@ -213,8 +207,10 @@ mod test {
stmt.execute_named(&[(":x", &"one")]).unwrap();
stmt.execute_named(&[(":y", &"two")]).unwrap();
let result: String = db.query_row("SELECT x FROM test WHERE y = 'two'", &[],
|row| row.get(0)).unwrap();
let result: String = db.query_row("SELECT x FROM test WHERE y = 'two'",
&[],
|row| row.get(0))
.unwrap();
assert_eq!(result, "one");
}
}

View File

@ -27,15 +27,16 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
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 s = String::from_utf8_lossy(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)
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
p_arg)
}
None => {
let nullptr: *mut c_void = ptr::null_mut();
@ -68,9 +69,8 @@ impl Connection {
unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
let trace_fn: fn(&str) = mem::transmute(p_arg);
let c_slice = CStr::from_ptr(z_sql).to_bytes();
if let Ok(s) = str::from_utf8(c_slice) {
trace_fn(s);
}
let s = String::from_utf8_lossy(c_slice);
trace_fn(&s);
}
let c = self.db.borrow_mut();
@ -94,13 +94,12 @@ impl Connection {
nanoseconds: u64) {
let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
let c_slice = 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 s = String::from_utf8_lossy(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 duration = Duration::new(nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32);
profile_fn(&s, duration);
}
let c = self.db.borrow_mut();

View File

@ -47,9 +47,7 @@ pub struct Transaction<'conn> {
impl<'conn> Transaction<'conn> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
pub fn new(conn: &Connection,
behavior: TransactionBehavior)
-> Result<Transaction> {
pub fn new(conn: &Connection, behavior: TransactionBehavior) -> Result<Transaction> {
let query = match behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
@ -91,7 +89,7 @@ impl<'conn> Transaction<'conn> {
/// tx.commit()
/// }
/// ```
pub fn savepoint<'a>(&'a self) -> Result<Transaction<'a>> {
pub fn savepoint(&self) -> Result<Transaction> {
self.conn.execute_batch("SAVEPOINT sp").map(|_| {
Transaction {
conn: self.conn,
@ -176,6 +174,7 @@ impl<'conn> Drop for Transaction<'conn> {
}
#[cfg(test)]
#[cfg_attr(feature="clippy", allow(similar_names))]
mod test {
use Connection;

239
src/types/chrono.rs Normal file
View File

@ -0,0 +1,239 @@
//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
extern crate chrono;
use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local};
use libc::c_int;
use {Error, Result};
use types::{FromSql, ToSql};
use ffi::sqlite3_stmt;
/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
impl ToSql for NaiveDate {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let date_str = self.format("%Y-%m-%d").to_string();
date_str.bind_parameter(stmt, col)
}
}
/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
impl FromSql for NaiveDate {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<NaiveDate> {
let s = try!(String::column_result(stmt, col));
match NaiveDate::parse_from_str(&s, "%Y-%m-%d") {
Ok(dt) => Ok(dt),
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
impl ToSql for NaiveTime {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let date_str = self.format("%H:%M:%S%.f").to_string();
date_str.bind_parameter(stmt, col)
}
}
/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
impl FromSql for NaiveTime {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<NaiveTime> {
let s = try!(String::column_result(stmt, col));
let fmt = match s.len() {
5 => "%H:%M",
8 => "%H:%M:%S",
_ => "%H:%M:%S%.f",
};
match NaiveTime::parse_from_str(&s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
/// ISO 8601 combined date and time without timezone => "YYYY-MM-DD HH:MM:SS.SSS"
impl ToSql for NaiveDateTime {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
date_str.bind_parameter(stmt, col)
}
}
/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date and time
/// without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported)
impl FromSql for NaiveDateTime {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<NaiveDateTime> {
let s = try!(String::column_result(stmt, col));
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%Y-%m-%dT%H:%M:%S%.f"
} else {
"%Y-%m-%d %H:%M:%S%.f"
};
match NaiveDateTime::parse_from_str(&s, fmt) {
Ok(dt) => Ok(dt),
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
impl<Tz: TimeZone> ToSql for DateTime<Tz> {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let utc_dt = self.with_timezone(&UTC);
utc_dt.to_rfc3339().bind_parameter(stmt, col)
}
}
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<UTC>.
impl FromSql for DateTime<UTC> {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<DateTime<UTC>> {
let s = {
let mut s = try!(String::column_result(stmt, col));
if s.len() >= 11 {
let sbytes = s.as_mut_vec();
if sbytes[10] == b' ' {
sbytes[10] = b'T';
}
}
s
};
match DateTime::parse_from_rfc3339(&s) {
Ok(dt) => Ok(dt.with_timezone(&UTC)),
Err(_) => NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime<Local>.
impl FromSql for DateTime<Local> {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<DateTime<Local>> {
let utc_dt = try!(DateTime::<UTC>::column_result(stmt, col));
Ok(utc_dt.with_timezone(&Local))
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
DateTime::<UTC>::column_has_valid_sqlite_type(stmt, col)
}
}
#[cfg(test)]
mod test {
use Connection;
use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC,
Duration};
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)").unwrap();
db
}
#[test]
fn test_naive_date() {
let db = checked_memory_handle();
let date = NaiveDate::from_ymd(2016, 2, 23);
db.execute("INSERT INTO foo (t) VALUES (?)", &[&date]).unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!("2016-02-23", s);
let t: NaiveDate = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(date, t);
}
#[test]
fn test_naive_time() {
let db = checked_memory_handle();
let time = NaiveTime::from_hms(23, 56, 4);
db.execute("INSERT INTO foo (t) VALUES (?)", &[&time]).unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!("23:56:04", s);
let v: NaiveTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(time, v);
}
#[test]
fn test_naive_date_time() {
let db = checked_memory_handle();
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms(23, 56, 4);
let dt = NaiveDateTime::new(date, time);
db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]).unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!("2016-02-23T23:56:04", s);
let v: NaiveDateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(dt, v);
db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS"
let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(dt, hms);
}
#[test]
fn test_date_time_utc() {
let db = checked_memory_handle();
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
let dt = NaiveDateTime::new(date, time);
let utc = UTC.from_utc_datetime(&dt);
db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc]).unwrap();
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!("2016-02-23T23:56:04.789+00:00", s);
let v1: DateTime<UTC> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(utc, v1);
let v2: DateTime<UTC> = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0))
.unwrap();
assert_eq!(utc, v2);
let v3: DateTime<UTC> = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0))
.unwrap();
assert_eq!(utc - Duration::milliseconds(789), v3);
let v4: DateTime<UTC> =
db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)).unwrap();
assert_eq!(utc, v4);
}
#[test]
fn test_date_time_local() {
let db = checked_memory_handle();
let date = NaiveDate::from_ymd(2016, 2, 23);
let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
let dt = NaiveDateTime::new(date, time);
let local = Local.from_local_datetime(&dt).single().unwrap();
db.execute("INSERT INTO foo (t) VALUES (?)", &[&local]).unwrap();
// Stored string should be in UTC
let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert!(s.ends_with("+00:00"));
let v: DateTime<Local> = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(local, v);
}
}

View File

@ -52,8 +52,6 @@
//! }
//! ```
extern crate time;
use libc::{c_int, c_double, c_char};
use std::ffi::CStr;
use std::mem;
@ -66,7 +64,11 @@ pub use ffi::sqlite3_column_type;
pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL};
const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S";
mod time;
#[cfg(feature = "chrono")]
mod chrono;
#[cfg(feature = "serde_json")]
mod serde_json;
/// A trait for types that can be converted into SQLite values.
pub trait ToSql {
@ -102,9 +104,10 @@ raw_to_impl!(c_double, sqlite3_bind_double);
impl ToSql for bool {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
match *self {
true => ffi::sqlite3_bind_int(stmt, col, 1),
_ => ffi::sqlite3_bind_int(stmt, col, 0),
if *self {
ffi::sqlite3_bind_int(stmt, col, 1)
} else {
ffi::sqlite3_bind_int(stmt, col, 0)
}
}
}
@ -153,13 +156,6 @@ impl ToSql for Vec<u8> {
}
}
impl ToSql for time::Timespec {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let time_str = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string();
time_str.bind_parameter(stmt, col)
}
}
impl<T: ToSql> ToSql for Option<T> {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
match *self {
@ -229,7 +225,7 @@ impl FromSql for String {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<String> {
let c_text = ffi::sqlite3_column_text(stmt, col);
if c_text.is_null() {
Ok("".to_string())
Ok("".to_owned())
} else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = try!(str::from_utf8(c_slice));
@ -262,28 +258,12 @@ impl FromSql for Vec<u8> {
}
}
impl FromSql for time::Timespec {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> {
let col_str = FromSql::column_result(stmt, col);
col_str.and_then(|txt: String| {
match time::strptime(&txt, SQLITE_DATETIME_FMT) {
Ok(tm) => Ok(tm.to_timespec()),
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
}
})
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
impl<T: FromSql> FromSql for Option<T> {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Option<T>> {
if sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL {
Ok(None)
} else {
FromSql::column_result(stmt, col).map(|t| Some(t))
FromSql::column_result(stmt, col).map(Some)
}
}
@ -312,26 +292,25 @@ pub enum Value {
impl FromSql for Value {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Value> {
match sqlite3_column_type(stmt, col) {
ffi::SQLITE_TEXT => FromSql::column_result(stmt, col).map(|t| Value::Text(t)),
ffi::SQLITE_TEXT => FromSql::column_result(stmt, col).map(Value::Text),
ffi::SQLITE_INTEGER => Ok(Value::Integer(ffi::sqlite3_column_int64(stmt, col))),
ffi::SQLITE_FLOAT => Ok(Value::Real(ffi::sqlite3_column_double(stmt, col))),
ffi::SQLITE_NULL => Ok(Value::Null),
ffi::SQLITE_BLOB => FromSql::column_result(stmt, col).map(|t| Value::Blob(t)),
ffi::SQLITE_BLOB => FromSql::column_result(stmt, col).map(Value::Blob),
_ => Err(Error::InvalidColumnType),
}
}
unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool {
true
}
}
#[cfg(test)]
#[cfg_attr(feature="clippy", allow(similar_names))]
mod test {
extern crate time;
use Connection;
use super::time;
use Error;
use libc::{c_int, c_double};
use std::f64::EPSILON;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@ -355,26 +334,12 @@ mod test {
let db = checked_memory_handle();
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_string()]).unwrap();
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_owned()]).unwrap();
let from: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(from, s);
}
#[test]
fn test_timespec() {
let db = checked_memory_handle();
let ts = time::Timespec {
sec: 10_000,
nsec: 0,
};
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(from, ts);
}
#[test]
fn test_option() {
let db = checked_memory_handle();
@ -402,6 +367,7 @@ mod test {
}
#[test]
#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))]
fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool {
match err {
@ -422,54 +388,52 @@ mod test {
let row = rows.next().unwrap().unwrap();
// check the correct types come back as expected
assert_eq!(vec![1, 2], row.get_checked::<i32,Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<i32,String>(1).unwrap());
assert_eq!(1, row.get_checked::<i32,c_int>(2).unwrap());
assert_eq!(1.5, row.get_checked::<i32,c_double>(3).unwrap());
assert!(row.get_checked::<i32,Option<c_int>>(4).unwrap().is_none());
assert!(row.get_checked::<i32,Option<c_double>>(4).unwrap().is_none());
assert!(row.get_checked::<i32,Option<String>>(4).unwrap().is_none());
assert_eq!(vec![1, 2], row.get_checked::<i32, Vec<u8>>(0).unwrap());
assert_eq!("text", row.get_checked::<i32, String>(1).unwrap());
assert_eq!(1, row.get_checked::<i32, c_int>(2).unwrap());
assert!((1.5 - row.get_checked::<i32, c_double>(3).unwrap()).abs() < EPSILON);
assert!(row.get_checked::<i32, Option<c_int>>(4).unwrap().is_none());
assert!(row.get_checked::<i32, Option<c_double>>(4).unwrap().is_none());
assert!(row.get_checked::<i32, Option<String>>(4).unwrap().is_none());
// check some invalid types
// 0 is actually a blob (Vec<u8>)
assert!(is_invalid_column_type(row.get_checked::<i32,c_int>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,c_int>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,i64>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,c_double>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,String>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,time::Timespec>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Option<c_int>>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, String>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(0).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(0).err().unwrap()));
// 1 is actually a text (String)
assert!(is_invalid_column_type(row.get_checked::<i32,c_int>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,i64>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,c_double>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Vec<u8>>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Option<c_int>>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(1).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(1).err().unwrap()));
// 2 is actually an integer
assert!(is_invalid_column_type(row.get_checked::<i32,c_double>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,String>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Vec<u8>>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,time::Timespec>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Option<c_double>>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, String>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(2).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_double>>(2).err().unwrap()));
// 3 is actually a float (c_double)
assert!(is_invalid_column_type(row.get_checked::<i32,c_int>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,i64>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,String>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Vec<u8>>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,time::Timespec>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Option<c_int>>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, String>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(3).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Option<c_int>>(3).err().unwrap()));
// 4 is actually NULL
assert!(is_invalid_column_type(row.get_checked::<i32,c_int>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,i64>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,c_double>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,String>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,Vec<u8>>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32,time::Timespec>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_int>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, i64>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, c_double>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, String>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, Vec<u8>>(4).err().unwrap()));
assert!(is_invalid_column_type(row.get_checked::<i32, time::Timespec>(4).err().unwrap()));
}
#[test]
@ -486,11 +450,14 @@ mod test {
let row = rows.next().unwrap().unwrap();
assert_eq!(Value::Blob(vec![1, 2]),
row.get_checked::<i32,Value>(0).unwrap());
row.get_checked::<i32, Value>(0).unwrap());
assert_eq!(Value::Text(String::from("text")),
row.get_checked::<i32,Value>(1).unwrap());
assert_eq!(Value::Integer(1), row.get_checked::<i32,Value>(2).unwrap());
assert_eq!(Value::Real(1.5), row.get_checked::<i32,Value>(3).unwrap());
assert_eq!(Value::Null, row.get_checked::<i32,Value>(4).unwrap());
row.get_checked::<i32, Value>(1).unwrap());
assert_eq!(Value::Integer(1), row.get_checked::<i32, Value>(2).unwrap());
match row.get_checked::<i32, Value>(3).unwrap() {
Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
x => panic!("Invalid Value {:?}", x),
}
assert_eq!(Value::Null, row.get_checked::<i32, Value>(4).unwrap());
}
}

66
src/types/serde_json.rs Normal file
View File

@ -0,0 +1,66 @@
//! `ToSql` and `FromSql` implementation for JSON `Value`.
extern crate serde_json;
use libc::c_int;
use self::serde_json::Value;
use {Error, Result};
use types::{FromSql, ToSql};
use ffi;
use ffi::sqlite3_stmt;
use ffi::sqlite3_column_type;
/// Serialize JSON `Value` to text.
impl ToSql for Value {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let s = serde_json::to_string(self).unwrap();
s.bind_parameter(stmt, col)
}
}
/// Deserialize text/blob to JSON `Value`.
impl FromSql for Value {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<Value> {
let value_result = match sqlite3_column_type(stmt, col) {
ffi::SQLITE_TEXT => {
let s = try!(String::column_result(stmt, col));
serde_json::from_str(&s)
}
ffi::SQLITE_BLOB => {
let blob = try!(Vec::<u8>::column_result(stmt, col));
serde_json::from_slice(&blob)
}
_ => return Err(Error::InvalidColumnType),
};
value_result.map_err(|err| Error::FromSqlConversionFailure(Box::new(err)))
}
}
#[cfg(test)]
mod test {
use Connection;
use super::serde_json;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)").unwrap();
db
}
#[test]
fn test_json_value() {
let db = checked_memory_handle();
let json = r#"{"foo": 13, "bar": "baz"}"#;
let data: serde_json::Value = serde_json::from_str(json).unwrap();
db.execute("INSERT INTO foo (t, b) VALUES (?, ?)",
&[&data, &json.as_bytes()])
.unwrap();
let t: serde_json::Value = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(data, t);
let b: serde_json::Value = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(data, b);
}
}

56
src/types/time.rs Normal file
View File

@ -0,0 +1,56 @@
extern crate time;
use libc::c_int;
use {Error, Result};
use types::{FromSql, ToSql};
use ffi::sqlite3_stmt;
const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S";
impl ToSql for time::Timespec {
unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int {
let time_str = time::at_utc(*self).strftime(SQLITE_DATETIME_FMT).unwrap().to_string();
time_str.bind_parameter(stmt, col)
}
}
impl FromSql for time::Timespec {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> {
let s = try!(String::column_result(stmt, col));
match time::strptime(&s, SQLITE_DATETIME_FMT) {
Ok(tm) => Ok(tm.to_timespec()),
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
}
}
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
String::column_has_valid_sqlite_type(stmt, col)
}
}
#[cfg(test)]
mod test {
use Connection;
use super::time;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)").unwrap();
db
}
#[test]
fn test_timespec() {
let db = checked_memory_handle();
let ts = time::Timespec {
sec: 10_000,
nsec: 0,
};
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(from, ts);
}
}