From 73e805a488b3f5ef56d6205534d6c6fe9a93780e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 1 Feb 2016 14:18:12 -0500 Subject: [PATCH 01/42] Update Changelog with changes from recent PR merges --- Changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.md b/Changelog.md index b7d2363..40c1642 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,9 @@ * 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. # Version 0.6.0 (2015-12-17) From 414938931803cccde663202330e4b123686eb25d Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 1 Feb 2016 14:19:51 -0500 Subject: [PATCH 02/42] Add AppVeyor build status to README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a813174..bc56bc3 100644 --- a/README.md +++ b/README.md @@ -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 From a793f8c8c57edbc23f69fdd0b171f3fea2d99996 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 1 Feb 2016 14:30:51 -0500 Subject: [PATCH 03/42] Remove scary lifetime-of-rows-may-panic from README. Closes #119. --- README.md | 39 --------------------------------------- src/lib.rs | 11 ++++------- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index bc56bc3..05635c7 100644 --- a/README.md +++ b/README.md @@ -73,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 { - 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 diff --git a/src/lib.rs b/src/lib.rs index d4b445e..7a11f64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -983,6 +983,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: @@ -994,7 +997,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 @@ -1008,12 +1011,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>, From 350fd11fed963b73f8e4b0ec206c57e0f4c636fe Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 1 Feb 2016 15:21:03 -0500 Subject: [PATCH 04/42] Add a `handle()` method to unsafely get the underlying SQLite connection. Doc comments suggest opening issues on rusqlite for any uses of `handle()`, as uses indicate areas where rusqlite insufficiently wraps SQLite. --- src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7a11f64..6b52a95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -494,6 +494,18 @@ impl Connection { 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) } From d6d9fa9f6324500e765cff9ca28047577406f732 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 1 Feb 2016 15:23:05 -0500 Subject: [PATCH 05/42] Add handle() to Changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 40c1642..63828da 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ * 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) From f529d130b92a10df6942bf880ed3509a1fffa7fb Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 2 Feb 2016 19:12:00 +0100 Subject: [PATCH 06/42] Rustfmt --- src/backup.rs | 4 +- src/blob.rs | 6 +- src/error.rs | 17 ++- src/lib.rs | 258 ++++++++++++++++++++++---------------------- src/named_params.rs | 26 ++--- src/transaction.rs | 4 +- src/types.rs | 82 +++++++------- 7 files changed, 197 insertions(+), 200 deletions(-) diff --git a/src/backup.rs b/src/backup.rs index e55c8ee..484171f 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -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> { + pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result> { Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main) } diff --git a/src/blob.rs b/src/blob.rs index f8b1710..9f50980 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -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); diff --git a/src/error.rs b/src/error.rs index ba93df8..3f356f6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -85,13 +85,18 @@ impl fmt::Display for Error { 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::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::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), @@ -111,13 +116,17 @@ impl error::Error for Error { 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::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::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", diff --git a/src/lib.rs b/src/lib.rs index d4b445e..d15b0e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,8 +87,8 @@ mod error; #[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; +#[cfg(feature = "functions")]pub mod functions; +#[cfg(feature = "blob")]pub mod blob; /// Old name for `Result`. `SqliteResult` is deprecated. pub type SqliteResult = Result; @@ -182,17 +182,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>(path: P, - flags: OpenFlags) - -> Result { - 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>(path: P, flags: OpenFlags) -> Result { + 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. /// @@ -249,9 +247,9 @@ impl Connection { /// Will return `Err` if the underlying SQLite call fails. pub fn transaction_with_behavior<'a>(&'a self, behavior: TransactionBehavior) - -> Result> { - Transaction::new(self, behavior) - } + -> Result> { + Transaction::new(self, behavior) + } /// Convenience method to run multiple SQL statements (that cannot take any parameters). /// @@ -359,7 +357,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(&self, sql: &str, params: &[&ToSql], f: F) -> result::Result + pub fn query_row_and_then(&self, + sql: &str, + params: &[&ToSql], + f: F) + -> result::Result where F: FnOnce(Row) -> result::Result, E: convert::From { @@ -390,9 +392,9 @@ impl Connection { /// does exactly the same thing. pub fn query_row_safe(&self, sql: &str, params: &[&ToSql], f: F) -> Result where F: FnOnce(Row) -> T - { - self.query_row(sql, params, f) - } + { + self.query_row(sql, params, f) + } /// Prepare a SQL statement for execution. /// @@ -490,9 +492,9 @@ impl Connection { pub fn load_extension>(&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) + } fn decode_result(&self, code: c_int) -> Result<()> { self.db.borrow_mut().decode_result(code) @@ -506,8 +508,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() } } @@ -542,60 +544,58 @@ impl Default for OpenFlags { } impl InnerConnection { - fn open_with_flags(c_path: &CString, - flags: OpenFlags) - -> Result { - 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 { + 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 + } 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 { - 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 @@ -621,10 +621,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) } } @@ -663,25 +663,22 @@ impl InnerConnection { unsafe { ffi::sqlite3_last_insert_rowid(self.db()) } } - fn prepare<'a>(&mut self, - conn: &'a Connection, - sql: &str) - -> Result> { - 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> { + 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()) } @@ -790,7 +787,7 @@ impl<'conn> Statement<'conn> { } else { Ok(self.conn.changes()) } - }, + } ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults), _ => Err(self.conn.decode_result(r).unwrap_err()), } @@ -839,19 +836,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> + pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) -> Result> 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 @@ -866,17 +860,17 @@ impl<'conn> Statement<'conn> { pub fn query_and_then<'a, T, E, F>(&'a mut self, params: &[&ToSql], f: F) - -> Result> + -> Result> where E: convert::From, F: FnMut(&Row) -> result::Result - { - 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. /// @@ -892,9 +886,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))); @@ -926,10 +920,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() } } @@ -963,15 +957,15 @@ pub struct AndThenRows<'stmt, F> { } impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> -where E: convert::From, - F: FnMut(&Row) -> result::Result + where E: convert::From, + F: FnMut(&Row) -> result::Result { type Item = result::Result; fn next(&mut self) -> Option { self.rows.next().map(|row_result| { row_result.map_err(E::from) - .and_then(|row| (self.map)(&row)) + .and_then(|row| (self.map)(&row)) }) } } @@ -1222,11 +1216,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()); - } + 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()); + } } #[test] @@ -1402,7 +1396,7 @@ mod test { assert_eq!(2i32, second.get(0)); - match first.get_checked::(0).unwrap_err() { + match first.get_checked::(0).unwrap_err() { Error::GetFromStaleRow => (), err => panic!("Unexpected error {}", err), } @@ -1450,7 +1444,7 @@ mod test { if version >= 3007016 { assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL) } - }, + } err => panic!("Unexpected error {}", err), } } diff --git a/src/named_params.rs b/src/named_params.rs index 4f55dd8..34b3c67 100644 --- a/src/named_params.rs +++ b/src/named_params.rs @@ -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(&self, - sql: &str, - params: &[(&str, &ToSql)], - f: F) - -> Result + pub fn query_row_named(&self, sql: &str, params: &[(&str, &ToSql)], f: F) -> Result 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 { 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> { + pub fn query_named<'a>(&'a mut self, params: &[(&str, &ToSql)]) -> Result> { 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 = db.query_row("SELECT y FROM test WHERE x = 'one'", &[], - |row| row.get(0)).unwrap(); + let result: Option = 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"); } } diff --git a/src/transaction.rs b/src/transaction.rs index 3adbdb2..ea09cef 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -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 { + pub fn new(conn: &Connection, behavior: TransactionBehavior) -> Result { let query = match behavior { TransactionBehavior::Deferred => "BEGIN DEFERRED", TransactionBehavior::Immediate => "BEGIN IMMEDIATE", diff --git a/src/types.rs b/src/types.rs index de3d709..c458e01 100644 --- a/src/types.rs +++ b/src/types.rs @@ -422,54 +422,54 @@ mod test { let row = rows.next().unwrap().unwrap(); // check the correct types come back as expected - assert_eq!(vec![1, 2], row.get_checked::>(0).unwrap()); - assert_eq!("text", row.get_checked::(1).unwrap()); - assert_eq!(1, row.get_checked::(2).unwrap()); - assert_eq!(1.5, row.get_checked::(3).unwrap()); - assert!(row.get_checked::>(4).unwrap().is_none()); - assert!(row.get_checked::>(4).unwrap().is_none()); - assert!(row.get_checked::>(4).unwrap().is_none()); + assert_eq!(vec![1, 2], row.get_checked::>(0).unwrap()); + assert_eq!("text", row.get_checked::(1).unwrap()); + assert_eq!(1, row.get_checked::(2).unwrap()); + assert_eq!(1.5, row.get_checked::(3).unwrap()); + assert!(row.get_checked::>(4).unwrap().is_none()); + assert!(row.get_checked::>(4).unwrap().is_none()); + assert!(row.get_checked::>(4).unwrap().is_none()); // check some invalid types // 0 is actually a blob (Vec) - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(0).err().unwrap())); // 1 is actually a text (String) - assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); // 2 is actually an integer - assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); // 3 is actually a float (c_double) - assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); // 4 is actually NULL - assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(4).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); } #[test] @@ -486,11 +486,11 @@ mod test { let row = rows.next().unwrap().unwrap(); assert_eq!(Value::Blob(vec![1, 2]), - row.get_checked::(0).unwrap()); + row.get_checked::(0).unwrap()); assert_eq!(Value::Text(String::from("text")), - row.get_checked::(1).unwrap()); - assert_eq!(Value::Integer(1), row.get_checked::(2).unwrap()); - assert_eq!(Value::Real(1.5), row.get_checked::(3).unwrap()); - assert_eq!(Value::Null, row.get_checked::(4).unwrap()); + row.get_checked::(1).unwrap()); + assert_eq!(Value::Integer(1), row.get_checked::(2).unwrap()); + assert_eq!(Value::Real(1.5), row.get_checked::(3).unwrap()); + assert_eq!(Value::Null, row.get_checked::(4).unwrap()); } } From d9fffaf7972af1796a6ea54608e65bfb28bad0fd Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Sat, 13 Feb 2016 10:51:24 +0800 Subject: [PATCH 07/42] Remove load_extension feature from the binding --- Cargo.toml | 2 +- libsqlite3-sys/Cargo.toml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3555180..45c954d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ license = "MIT" name = "rusqlite" [features] -load_extension = ["libsqlite3-sys/load_extension"] +load_extension = [] backup = [] blob = [] functions = [] diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index b199cfc..b93650b 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -8,9 +8,6 @@ license = "MIT" links = "sqlite3" build = "build.rs" -[features] -load_extension = [] - [build-dependencies] pkg-config = "~0.3" From 0fe1990d34329c61b07f2c728546a27a26f2ddd6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 14 Feb 2016 16:11:59 +0100 Subject: [PATCH 08/42] Fix clippy warnings --- Cargo.toml | 1 + src/error.rs | 100 ++++++++++++++++++++++----------------------- src/functions.rs | 15 ++++--- src/lib.rs | 22 ++++------ src/transaction.rs | 2 +- src/types.rs | 17 ++++---- 6 files changed, 80 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3555180..378eee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ trace = [] time = "~0.1.0" bitflags = "~0.1" libc = "~0.2" +clippy = {version = "~0.0.41", optional = true} [dev-dependencies] tempdir = "~0.3.4" diff --git a/src/error.rs b/src/error.rs index 3f356f6..8348dcc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -82,84 +82,84 @@ 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 => { + 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 => { + 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::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"), #[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::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 => { + 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 => { + 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::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", #[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::UserFunctionError(ref err) => err.description(), } } 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::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, #[cfg(feature = "functions")] - &Error::InvalidFunctionParameterType => None, + Error::InvalidFunctionParameterType => None, #[cfg(feature = "functions")] - &Error::UserFunctionError(ref err) => Some(&**err), + Error::UserFunctionError(ref err) => Some(&**err), } } } diff --git a/src/functions.rs b/src/functions.rs index 49e0032..e00221a 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -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 { 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 FromValue for Option { 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`. /// diff --git a/src/lib.rs b/src/lib.rs index f81926b..9d1c7d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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] @@ -99,7 +102,7 @@ pub type Result = result::Result; 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() + utf8_str.unwrap_or("Invalid string encoding").to_owned() } fn str_to_cstring(s: &str) -> Result { @@ -127,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 { + fn to_cstring(&self) -> Result { 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), @@ -234,7 +237,7 @@ impl Connection { /// # Failure /// /// Will return `Err` if the underlying SQLite call fails. - pub fn transaction<'a>(&'a self) -> Result> { + pub fn transaction(&self) -> Result { Transaction::new(self, TransactionBehavior::Deferred) } @@ -245,9 +248,7 @@ impl Connection { /// # Failure /// /// Will return `Err` if the underlying SQLite call fails. - pub fn transaction_with_behavior<'a>(&'a self, - behavior: TransactionBehavior) - -> Result> { + pub fn transaction_with_behavior(&self, behavior: TransactionBehavior) -> Result { Transaction::new(self, behavior) } @@ -572,11 +573,7 @@ impl InnerConnection { // 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 - } else { - false - }; + let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC; ffi::sqlite3_mutex_free(mutex_ptr); if is_singlethreaded { return Err(Error::SqliteSingleThreadedMode); @@ -1173,7 +1170,6 @@ impl<'a> RowIndex for &'a str { #[cfg(test)] mod test { - extern crate libsqlite3_sys as ffi; extern crate tempdir; pub use super::*; use self::tempdir::TempDir; diff --git a/src/transaction.rs b/src/transaction.rs index ea09cef..5ef4909 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -89,7 +89,7 @@ impl<'conn> Transaction<'conn> { /// tx.commit() /// } /// ``` - pub fn savepoint<'a>(&'a self) -> Result> { + pub fn savepoint(&self) -> Result { self.conn.execute_batch("SAVEPOINT sp").map(|_| { Transaction { conn: self.conn, diff --git a/src/types.rs b/src/types.rs index c458e01..aca7fad 100644 --- a/src/types.rs +++ b/src/types.rs @@ -102,9 +102,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) } } } @@ -229,7 +230,7 @@ impl FromSql for String { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { 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)); @@ -283,7 +284,7 @@ impl FromSql for Option { 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,11 +313,11 @@ pub enum Value { impl FromSql for Value { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { 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), } } @@ -355,7 +356,7 @@ 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); From aea2f876d53b068d086112395300935aeaf8224e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 14 Feb 2016 16:24:35 +0100 Subject: [PATCH 09/42] Try to fix regression with Rust stable. --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 9d1c7d6..9c0f322 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1172,6 +1172,7 @@ impl<'a> RowIndex for &'a str { mod test { extern crate tempdir; pub use super::*; + use ffi; use self::tempdir::TempDir; pub use std::error::Error as StdError; pub use std::fmt; From bdb9823b07bc9b8964ec0d03365f85f83c8b6ee6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 22 Feb 2016 20:36:49 +0100 Subject: [PATCH 10/42] Add From/ToSql impl. for chrono types. --- Cargo.toml | 1 + src/types/chrono.rs | 267 +++++++++++++++++++++++++++++++++ src/{types.rs => types/mod.rs} | 3 + 3 files changed, 271 insertions(+) create mode 100644 src/types/chrono.rs rename src/{types.rs => types/mod.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index 3555180..471995d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ trace = [] time = "~0.1.0" bitflags = "~0.1" libc = "~0.2" +chrono = { version = "~0.2", optional = true } [dev-dependencies] tempdir = "~0.3.4" diff --git a/src/types/chrono.rs b/src/types/chrono.rs new file mode 100644 index 0000000..9dbacfe --- /dev/null +++ b/src/types/chrono.rs @@ -0,0 +1,267 @@ +//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. +extern crate chrono; + +use std::error; +use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local}; +use libc::c_int; + +use {Error, Result}; +use types::{FromSql, ToSql}; + +use ffi; +use ffi::sqlite3_stmt; +use ffi::sqlite3_column_type; + +const JULIAN_DAY: f64 = 2440587.5; // 1970-01-01 00:00:00 is JD 2440587.5 +const DAY_IN_SECONDS: f64 = 86400.0; +const JULIAN_DAY_GREGORIAN: f64 = 1721424.5; // Jan 1, 1 proleptic Gregorian calendar + +/// 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" or Julian Day => ISO 8601 calendar date without timezone. +impl FromSql for NaiveDate { + unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + 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))), + } + } + ffi::SQLITE_FLOAT => { + // if column affinity is REAL and an integer/unix timestamp is inserted => unexpected result + let mut jd = ffi::sqlite3_column_double(stmt, col); + jd -= JULIAN_DAY_GREGORIAN; + if jd < i32::min_value() as f64 || jd > i32::max_value() as f64 { + let err: Box = "out-of-range date".into(); + return Err(Error::FromSqlConversionFailure(err)); + } + match NaiveDate::from_num_days_from_ce_opt(jd as i32) { + Some(dt) => Ok(dt), + None => { + let err: Box = "out-of-range date".into(); + Err(Error::FromSqlConversionFailure(err)) + } + } + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + let sqlite_type = sqlite3_column_type(stmt, col); + sqlite_type == ffi::SQLITE_TEXT || sqlite_type == ffi::SQLITE_FLOAT + } +} + +/// 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.3f").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 { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + let fmt = match s.len() { + 5 => "%H:%M", + 8 => "%H:%M:%S", + _ => "%H:%M:%S%.3f", + }; + match NaiveTime::parse_from_str(&s, fmt) { + Ok(dt) => Ok(dt), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + sqlite3_column_type(stmt, col) == ffi::SQLITE_TEXT + } +} + +/// 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-%d %H:%M:%S%.3f").to_string(); + date_str.bind_parameter(stmt, col) + } +} + +/// "YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 combined date and time without timezone. +/// ("YYYY-MM-DDTHH:MM"/"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 { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + let fmt = match s.len() { + 16 => { + match s.as_bytes()[10] { + b'T' => "%Y-%m-%dT%H:%M", + _ => "%Y-%m-%d %H:%M", + } + } + 19 => { + match s.as_bytes()[10] { + b'T' => "%Y-%m-%dT%H:%M:%S", + _ => "%Y-%m-%d %H:%M:%S", + } + } + _ => { + match s.as_bytes()[10] { + b'T' => "%Y-%m-%dT%H:%M:%S%.3f", + _ => "%Y-%m-%d %H:%M:%S%.3f", + } + } + }; + match NaiveDateTime::parse_from_str(&s, fmt) { + Ok(dt) => Ok(dt), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } + ffi::SQLITE_INTEGER => { + match NaiveDateTime::from_timestamp_opt(ffi::sqlite3_column_int64(stmt, col), 0) { + Some(dt) => Ok(dt), + None => { + let err: Box = "out-of-range number of seconds" + .into(); + Err(Error::FromSqlConversionFailure(err)) + } + } + } + ffi::SQLITE_FLOAT => { + // if column affinity is REAL and an integer/unix timestamp is inserted => unexpected result + let mut jd = ffi::sqlite3_column_double(stmt, col); + jd -= JULIAN_DAY; + jd *= DAY_IN_SECONDS; + let ns = jd.fract() * 10f64.powi(9); + match NaiveDateTime::from_timestamp_opt(jd as i64, ns as u32) { + Some(dt) => Ok(dt), + None => { + let err: Box = "out-of-range number of \ + seconds and/or invalid \ + nanosecond" + .into(); + Err(Error::FromSqlConversionFailure(err)) + } + } + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + let sqlite_type = sqlite3_column_type(stmt, col); + sqlite_type == ffi::SQLITE_TEXT || sqlite_type == ffi::SQLITE_INTEGER || + sqlite_type == ffi::SQLITE_FLOAT + } +} + +/// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" +impl ToSql for DateTime { + 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%.3f%:z").to_string(); + date_str.bind_parameter(stmt, col) + } +} + +/// "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 date and time with time zone. +/// ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) +/// When the timezone is not specified, UTC is used. +impl FromSql for DateTime { + unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + if s.len() > 23 { + let fmt = if s.as_bytes()[10] == b'T' { + "%Y-%m-%dT%H:%M:%S%.3f%:z" + } else { + "%Y-%m-%d %H:%M:%S%.3f%:z" + }; + match UTC.datetime_from_str(fmt, &s) { + Ok(dt) => Ok(dt), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } else { + NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) + } + } + ffi::SQLITE_INTEGER => { + NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) + } + ffi::SQLITE_FLOAT => { + NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + NaiveDateTime::column_has_valid_sqlite_type(stmt, col) + } +} + + +/// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" +impl ToSql for DateTime { + 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%.3f%:z").to_string(); + date_str.bind_parameter(stmt, col) + } +} + +/// "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 date and time with time zone. +/// ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) +/// When the timezone is not specified, Local is used. +impl FromSql for DateTime { + unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + if s.len() > 23 { + let fmt = if s.as_bytes()[10] == b'T' { + "%Y-%m-%dT%H:%M:%S%.3f%:z" + } else { + "%Y-%m-%d %H:%M:%S%.3f%:z" + }; + match Local.datetime_from_str(fmt, &s) { + Ok(dt) => Ok(dt), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } else { + NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) + } + } + ffi::SQLITE_INTEGER => { + NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) + } + ffi::SQLITE_FLOAT => { + NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + NaiveDateTime::column_has_valid_sqlite_type(stmt, col) + } +} + +// struct UnixTime(NaiveDateTime); +// struct JulianTime(NaiveDateTime) diff --git a/src/types.rs b/src/types/mod.rs similarity index 99% rename from src/types.rs rename to src/types/mod.rs index c458e01..3f33c85 100644 --- a/src/types.rs +++ b/src/types/mod.rs @@ -66,6 +66,9 @@ pub use ffi::sqlite3_column_type; pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL}; +#[cfg(feature = "chrono")] +mod chrono; + const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; /// A trait for types that can be converted into SQLite values. From 33bc6edb4a62b961f493abb20135117b2a1a2df0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 23 Feb 2016 18:18:56 +0100 Subject: [PATCH 11/42] Test From/ToSQL for chrono types. --- src/types/chrono.rs | 125 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 9dbacfe..41dd024 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -64,7 +64,7 @@ impl FromSql for NaiveDate { /// 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.3f").to_string(); + let date_str = self.format("%H:%M:%S%.3f").to_string(); date_str.bind_parameter(stmt, col) } } @@ -175,7 +175,7 @@ impl FromSql for NaiveDateTime { /// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" impl ToSql for DateTime { 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%.3f%:z").to_string(); + let date_str = self.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(); date_str.bind_parameter(stmt, col) } } @@ -194,7 +194,7 @@ impl FromSql for DateTime { } else { "%Y-%m-%d %H:%M:%S%.3f%:z" }; - match UTC.datetime_from_str(fmt, &s) { + match UTC.datetime_from_str(&s, fmt) { Ok(dt) => Ok(dt), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), } @@ -221,7 +221,7 @@ impl FromSql for DateTime { /// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" impl ToSql for DateTime { 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%.3f%:z").to_string(); + let date_str = self.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(); date_str.bind_parameter(stmt, col) } } @@ -240,7 +240,7 @@ impl FromSql for DateTime { } else { "%Y-%m-%d %H:%M:%S%.3f%:z" }; - match Local.datetime_from_str(fmt, &s) { + match Local.datetime_from_str(&s, fmt) { Ok(dt) => Ok(dt), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), } @@ -265,3 +265,118 @@ impl FromSql for DateTime { // struct UnixTime(NaiveDateTime); // struct JulianTime(NaiveDateTime) + +#[cfg(test)] +mod test { + use Connection; + use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC}; + + fn checked_memory_handle() -> Connection { + let db = Connection::open_in_memory().unwrap(); + db.execute_batch("CREATE TABLE chrono (t TEXT, i INTEGER, f FLOAT, b BLOB)").unwrap(); + db + } + + #[test] + fn test_naive_date() { + let db = checked_memory_handle(); + let d = NaiveDate::from_ymd(2016, 2, 23); + db.execute("INSERT INTO chrono (t) VALUES (?)", &[&d]).unwrap(); + db.execute("UPDATE chrono SET f = julianday(t)", &[]).unwrap(); + + let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23", s); + let t: NaiveDate = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(d, t); + let f: NaiveDate = db.query_row("SELECT f FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(d, f); + } + + #[test] + fn test_naive_time() { + let db = checked_memory_handle(); + let t = NaiveTime::from_hms(23, 56, 4); + db.execute("INSERT INTO chrono (t) VALUES (?)", &[&t]).unwrap(); + + let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!("23:56:04.000", s); + let v: NaiveTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(t, v); + } + + #[test] + fn test_naive_date_time() { + let db = checked_memory_handle(); + let d = NaiveDate::from_ymd(2016, 2, 23); + let t = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(d, t); + + let di = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 3)); + let ds = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 5)); + + db.execute("INSERT INTO chrono (t) VALUES (?)", &[&dt]).unwrap(); + db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + &[]) + .unwrap(); + + let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23 23:56:04.000", s); + let v: NaiveDateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(dt, v); + let f: NaiveDateTime = db.query_row("SELECT f FROM chrono", &[], |r| r.get(0)).unwrap(); + // FIXME `2016-02-23T23:56:04` vs `2016-02-23T23:56:03.999992609` + assert!(f.ge(&di) && f.le(&ds)); + let i: NaiveDateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(dt, i); + + db.execute("UPDATE chrono set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS" + let b: NaiveDateTime = db.query_row("SELECT b FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(dt, b); + + db.execute("UPDATE chrono set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); + let b: NaiveDateTime = db.query_row("SELECT b FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 0)), b); + } + + #[test] + fn test_date_time_utc() { + let db = checked_memory_handle(); + let d = NaiveDate::from_ymd(2016, 2, 23); + let t = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(d, t); + let utc = UTC.from_utc_datetime(&dt); + + db.execute("INSERT INTO chrono (t) VALUES (?)", &[&utc]).unwrap(); + db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + &[]) + .unwrap(); + + let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23 23:56:04.000+00:00", s); + let v: DateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc, v); + let i: DateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc, i); + } + + #[test] + fn test_date_time_local() { + let db = checked_memory_handle(); + let d = NaiveDate::from_ymd(2016, 2, 23); + let t = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(d, t); + let local = Local.from_local_datetime(&dt).single().unwrap(); + + db.execute("INSERT INTO chrono (t) VALUES (?)", &[&local]).unwrap(); + db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + &[]) + .unwrap(); + + let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23 23:56:04.000+01:00", s); + let v: DateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(local, v); + let i: DateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + assert_eq!(local, i); + } +} From df245d17184e21877da115e2951e07db1a72c000 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 23 Feb 2016 18:28:39 +0100 Subject: [PATCH 12/42] Add chrono feature to CI files. --- .travis.yml | 3 ++- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89334cf..e0d5eb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,5 @@ 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 "backup blob chrono functions load_extension trace" diff --git a/appveyor.yml b/appveyor.yml index 9fb260e..0d9a5f3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 trace" cache: - C:\Users\appveyor\.cargo From 08d4d9aadf881802d729f9e925f35e1353002da5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 23 Feb 2016 18:34:08 +0100 Subject: [PATCH 13/42] Comment out TZ sensitive tests. --- src/types/chrono.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 41dd024..2a71955 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -372,8 +372,8 @@ mod test { &[]) .unwrap(); - let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); - assert_eq!("2016-02-23 23:56:04.000+01:00", s); + //let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + //assert_eq!("2016-02-23 23:56:04.000+01:00", s); let v: DateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); assert_eq!(local, v); let i: DateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); From ecac52dc4cf5abffa98fd64f530882b334d71daf Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Feb 2016 18:44:53 +0100 Subject: [PATCH 14/42] Make FromSql impl for TimeSpec support double/int. --- src/types/chrono.rs | 130 +++++++++++++++++++++----------------------- src/types/mod.rs | 47 +--------------- src/types/time.rs | 81 +++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 113 deletions(-) create mode 100644 src/types/time.rs diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 2a71955..dc1718f 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -55,16 +55,15 @@ impl FromSql for NaiveDate { } } - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - let sqlite_type = sqlite3_column_type(stmt, col); - sqlite_type == ffi::SQLITE_TEXT || sqlite_type == ffi::SQLITE_FLOAT + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check } } /// 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%.3f").to_string(); + let date_str = self.format("%H:%M:%S%.f").to_string(); date_str.bind_parameter(stmt, col) } } @@ -72,20 +71,15 @@ impl ToSql for NaiveTime { /// "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 { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - let s = try!(String::column_result(stmt, col)); - let fmt = match s.len() { - 5 => "%H:%M", - 8 => "%H:%M:%S", - _ => "%H:%M:%S%.3f", - }; - match NaiveTime::parse_from_str(&s, fmt) { - Ok(dt) => Ok(dt), - Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } - } - _ => Err(Error::InvalidColumnType), + 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))), } } @@ -97,7 +91,7 @@ impl FromSql for NaiveTime { /// 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-%d %H:%M:%S%.3f").to_string(); + let date_str = self.format("%Y-%m-%d %H:%M:%S%.f").to_string(); date_str.bind_parameter(stmt, col) } } @@ -124,8 +118,8 @@ impl FromSql for NaiveDateTime { } _ => { match s.as_bytes()[10] { - b'T' => "%Y-%m-%dT%H:%M:%S%.3f", - _ => "%Y-%m-%d %H:%M:%S%.3f", + b'T' => "%Y-%m-%dT%H:%M:%S%.f", + _ => "%Y-%m-%d %H:%M:%S%.f", } } }; @@ -165,17 +159,15 @@ impl FromSql for NaiveDateTime { } } - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - let sqlite_type = sqlite3_column_type(stmt, col); - sqlite_type == ffi::SQLITE_TEXT || sqlite_type == ffi::SQLITE_INTEGER || - sqlite_type == ffi::SQLITE_FLOAT + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check } } /// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" impl ToSql for DateTime { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - let date_str = self.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(); + let date_str = self.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); date_str.bind_parameter(stmt, col) } } @@ -190,9 +182,9 @@ impl FromSql for DateTime { let s = try!(String::column_result(stmt, col)); if s.len() > 23 { let fmt = if s.as_bytes()[10] == b'T' { - "%Y-%m-%dT%H:%M:%S%.3f%:z" + "%Y-%m-%dT%H:%M:%S%.f%:z" } else { - "%Y-%m-%d %H:%M:%S%.3f%:z" + "%Y-%m-%d %H:%M:%S%.f%:z" }; match UTC.datetime_from_str(&s, fmt) { Ok(dt) => Ok(dt), @@ -212,8 +204,8 @@ impl FromSql for DateTime { } } - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - NaiveDateTime::column_has_valid_sqlite_type(stmt, col) + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check } } @@ -221,7 +213,7 @@ impl FromSql for DateTime { /// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" impl ToSql for DateTime { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - let date_str = self.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(); + let date_str = self.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); date_str.bind_parameter(stmt, col) } } @@ -236,9 +228,9 @@ impl FromSql for DateTime { let s = try!(String::column_result(stmt, col)); if s.len() > 23 { let fmt = if s.as_bytes()[10] == b'T' { - "%Y-%m-%dT%H:%M:%S%.3f%:z" + "%Y-%m-%dT%H:%M:%S%.f%:z" } else { - "%Y-%m-%d %H:%M:%S%.3f%:z" + "%Y-%m-%d %H:%M:%S%.f%:z" }; match Local.datetime_from_str(&s, fmt) { Ok(dt) => Ok(dt), @@ -258,8 +250,8 @@ impl FromSql for DateTime { } } - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - NaiveDateTime::column_has_valid_sqlite_type(stmt, col) + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check } } @@ -273,7 +265,7 @@ mod test { fn checked_memory_handle() -> Connection { let db = Connection::open_in_memory().unwrap(); - db.execute_batch("CREATE TABLE chrono (t TEXT, i INTEGER, f FLOAT, b BLOB)").unwrap(); + db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)").unwrap(); db } @@ -281,14 +273,14 @@ mod test { fn test_naive_date() { let db = checked_memory_handle(); let d = NaiveDate::from_ymd(2016, 2, 23); - db.execute("INSERT INTO chrono (t) VALUES (?)", &[&d]).unwrap(); - db.execute("UPDATE chrono SET f = julianday(t)", &[]).unwrap(); + db.execute("INSERT INTO foo (t) VALUES (?)", &[&d]).unwrap(); + db.execute("UPDATE foo SET f = julianday(t)", &[]).unwrap(); - let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).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 chrono", &[], |r| r.get(0)).unwrap(); + let t: NaiveDate = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(d, t); - let f: NaiveDate = db.query_row("SELECT f FROM chrono", &[], |r| r.get(0)).unwrap(); + let f: NaiveDate = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(d, f); } @@ -296,11 +288,11 @@ mod test { fn test_naive_time() { let db = checked_memory_handle(); let t = NaiveTime::from_hms(23, 56, 4); - db.execute("INSERT INTO chrono (t) VALUES (?)", &[&t]).unwrap(); + db.execute("INSERT INTO foo (t) VALUES (?)", &[&t]).unwrap(); - let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); - assert_eq!("23:56:04.000", s); - let v: NaiveTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).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!(t, v); } @@ -314,27 +306,27 @@ mod test { let di = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 3)); let ds = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 5)); - db.execute("INSERT INTO chrono (t) VALUES (?)", &[&dt]).unwrap(); - db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]).unwrap(); + db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", &[]) .unwrap(); - let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); - assert_eq!("2016-02-23 23:56:04.000", s); - let v: NaiveDateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23 23:56:04", s); + let v: NaiveDateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(dt, v); - let f: NaiveDateTime = db.query_row("SELECT f FROM chrono", &[], |r| r.get(0)).unwrap(); - // FIXME `2016-02-23T23:56:04` vs `2016-02-23T23:56:03.999992609` + let f: NaiveDateTime = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); + // `2016-02-23T23:56:04` vs `2016-02-23T23:56:03.999992609` assert!(f.ge(&di) && f.le(&ds)); - let i: NaiveDateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + let i: NaiveDateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(dt, i); - db.execute("UPDATE chrono set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS" - let b: NaiveDateTime = db.query_row("SELECT b FROM chrono", &[], |r| r.get(0)).unwrap(); + db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS" + let b: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(dt, b); - db.execute("UPDATE chrono set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); - let b: NaiveDateTime = db.query_row("SELECT b FROM chrono", &[], |r| r.get(0)).unwrap(); + db.execute("UPDATE foo set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); + let b: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 0)), b); } @@ -346,16 +338,16 @@ mod test { let dt = NaiveDateTime::new(d, t); let utc = UTC.from_utc_datetime(&dt); - db.execute("INSERT INTO chrono (t) VALUES (?)", &[&utc]).unwrap(); - db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc]).unwrap(); + db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", &[]) .unwrap(); - let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); - assert_eq!("2016-02-23 23:56:04.000+00:00", s); - let v: DateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!("2016-02-23 23:56:04+00:00", s); + let v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(utc, v); - let i: DateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + let i: DateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(utc, i); } @@ -367,16 +359,16 @@ mod test { let dt = NaiveDateTime::new(d, t); let local = Local.from_local_datetime(&dt).single().unwrap(); - db.execute("INSERT INTO chrono (t) VALUES (?)", &[&local]).unwrap(); - db.execute("UPDATE chrono SET f = julianday(t), i = strftime('%s', t)", + db.execute("INSERT INTO foo (t) VALUES (?)", &[&local]).unwrap(); + db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", &[]) .unwrap(); - //let s: String = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); - //assert_eq!("2016-02-23 23:56:04.000+01:00", s); - let v: DateTime = db.query_row("SELECT t FROM chrono", &[], |r| r.get(0)).unwrap(); + // let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + // assert_eq!("2016-02-23 23:56:04.000+01:00", s); + let v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(local, v); - let i: DateTime = db.query_row("SELECT i FROM chrono", &[], |r| r.get(0)).unwrap(); + let i: DateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(local, i); } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 3f33c85..2b33fba 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -52,8 +52,6 @@ //! } //! ``` -extern crate time; - use libc::{c_int, c_double, c_char}; use std::ffi::CStr; use std::mem; @@ -66,11 +64,10 @@ pub use ffi::sqlite3_column_type; pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL}; +mod time; #[cfg(feature = "chrono")] mod chrono; -const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; - /// A trait for types that can be converted into SQLite values. pub trait ToSql { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int; @@ -156,13 +153,6 @@ impl ToSql for Vec { } } -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 ToSql for Option { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { match *self { @@ -265,22 +255,6 @@ impl FromSql for Vec { } } -impl FromSql for time::Timespec { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - 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 FromSql for Option { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { if sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL { @@ -331,8 +305,9 @@ impl FromSql for Value { #[cfg(test)] mod test { + extern crate time; + use Connection; - use super::time; use Error; use libc::{c_int, c_double}; @@ -364,20 +339,6 @@ mod test { 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(); @@ -455,7 +416,6 @@ mod test { assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); // 3 is actually a float (c_double) @@ -463,7 +423,6 @@ mod test { assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); // 4 is actually NULL diff --git a/src/types/time.rs b/src/types/time.rs new file mode 100644 index 0000000..30886a4 --- /dev/null +++ b/src/types/time.rs @@ -0,0 +1,81 @@ +extern crate time; + +use libc::c_int; +use {Error, Result}; +use types::{FromSql, ToSql}; + +use ffi; +use ffi::sqlite3_stmt; +use ffi::sqlite3_column_type; + +const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; +const JULIAN_DAY: f64 = 2440587.5; // 1970-01-01 00:00:00 is JD 2440587.5 +const DAY_IN_SECONDS: f64 = 86400.0; + +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 { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + 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))), + } + }) + } + ffi::SQLITE_INTEGER => Ok(time::Timespec::new(ffi::sqlite3_column_int64(stmt, col), 0)), + ffi::SQLITE_FLOAT => { + let mut jd = ffi::sqlite3_column_double(stmt, col); + jd -= JULIAN_DAY; + jd *= DAY_IN_SECONDS; + let ns = jd.fract() * 10f64.powi(9); + Ok(time::Timespec::new(jd as i64, ns as i32)) + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check + } +} + +#[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, i) VALUES (?, ?)", &[&ts, &ts.sec]).unwrap(); + db.execute("UPDATE foo SET f = julianday(t)", &[]).unwrap(); + + let from: time::Timespec = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(from, ts); + let from: time::Timespec = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(from, ts); + // `Timespec { sec: 9999, nsec: 999994039 }` vs `Timespec{ sec: 10000, nsec: 0 }` + let from: time::Timespec = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); + assert!((from.sec - ts.sec).abs() <= 1); + } +} From 3c1ce6428a7ef9d1b05a2bd4d13f30a0c8eeb742 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Feb 2016 19:06:37 +0100 Subject: [PATCH 15/42] Implement FromSql/ToSql for serde_json Value --- Cargo.toml | 1 + src/types/mod.rs | 2 ++ src/types/serde_json.rs | 75 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/types/serde_json.rs diff --git a/Cargo.toml b/Cargo.toml index 471995d..23c3cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ time = "~0.1.0" bitflags = "~0.1" libc = "~0.2" chrono = { version = "~0.2", optional = true } +serde_json = { version = "0.6", optional = true } [dev-dependencies] tempdir = "~0.3.4" diff --git a/src/types/mod.rs b/src/types/mod.rs index 2b33fba..8c60f31 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -67,6 +67,8 @@ pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NUL 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 { diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs new file mode 100644 index 0000000..251e08a --- /dev/null +++ b/src/types/serde_json.rs @@ -0,0 +1,75 @@ +//! `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 { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => { + let s = try!(String::column_result(stmt, col)); + match serde_json::from_str(&s) { + Ok(v) => Ok(v), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } + ffi::SQLITE_BLOB => { + let blob: Vec = try!(FromSql::column_result(stmt, col)); + match serde_json::from_slice(&blob[..]) { + Ok(v) => Ok(v), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + } + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true // to avoid double check + } +} + +#[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 = "{\"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); + } +} From 2bd54578f5070c74660035d034e146d7daddddaa Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 11:54:02 -0400 Subject: [PATCH 16/42] Update clippy version and address new warnings --- Cargo.toml | 2 +- src/error.rs | 17 +++++++++-------- src/lib.rs | 9 ++++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 378eee1..1eb6cd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ trace = [] time = "~0.1.0" bitflags = "~0.1" libc = "~0.2" -clippy = {version = "~0.0.41", optional = true} +clippy = {version = "~0.0.58", optional = true} [dev-dependencies] tempdir = "~0.3.4" diff --git a/src/error.rs b/src/error.rs index 8348dcc..3662d37 100644 --- a/src/error.rs +++ b/src/error.rs @@ -143,18 +143,19 @@ impl error::Error for Error { 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::SqliteSingleThreadedMode | + Error::InvalidParameterName(_) | + Error::ExecuteReturnedResults | + Error::QueryReturnedNoRows | + Error::GetFromStaleRow | + Error::InvalidColumnIndex(_) | + Error::InvalidColumnName(_) | + Error::InvalidColumnType | Error::InvalidPath(_) => None, - Error::ExecuteReturnedResults => None, - Error::QueryReturnedNoRows => None, - Error::GetFromStaleRow => None, - Error::InvalidColumnIndex(_) => None, - Error::InvalidColumnName(_) => None, - Error::InvalidColumnType => None, #[cfg(feature = "functions")] Error::InvalidFunctionParameterType => None, diff --git a/src/lib.rs b/src/lib.rs index 9c0f322..952ebe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,9 @@ #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] +// Clippy complains about SQLite in our doc comments, but they're fine. +#![cfg_attr(feature="clippy", allow(doc_markdown))] + extern crate libc; extern crate libsqlite3_sys as ffi; #[macro_use] @@ -791,10 +794,10 @@ 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), From 41fe698cecdda72250d411bb7f78d811db1304b2 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 14:18:56 -0400 Subject: [PATCH 17/42] Address additional clippy warnings --- src/error.rs | 2 ++ src/functions.rs | 11 ++++++----- src/lib.rs | 10 +++------- src/transaction.rs | 1 + src/types.rs | 10 ++++++++-- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3662d37..ebc79b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -140,6 +140,7 @@ impl error::Error for Error { } } + #[allow(match_same_arms)] fn cause(&self) -> Option<&error::Error> { match *self { Error::SqliteFailure(ref err, _) => Some(err), @@ -159,6 +160,7 @@ impl error::Error for Error { #[cfg(feature = "functions")] Error::InvalidFunctionParameterType => None, + #[cfg(feature = "functions")] Error::UserFunctionError(ref err) => Some(&**err), } diff --git a/src/functions.rs b/src/functions.rs index e00221a..8fd78f0 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -307,7 +307,7 @@ impl<'a> Context<'a> { ffi::sqlite3_set_auxdata(self.ctx, arg, mem::transmute(boxed), - Some(mem::transmute(free_boxed_value::))) + Some(free_boxed_value::)) }; } @@ -480,7 +480,7 @@ impl InnerConnection { Some(call_boxed_closure::), None, None, - Some(mem::transmute(free_boxed_value::))) + Some(free_boxed_value::)) }; self.decode_result(r) } @@ -597,7 +597,7 @@ impl InnerConnection { None, Some(call_boxed_step::), Some(call_boxed_final::), - Some(mem::transmute(free_boxed_value::))) + Some(free_boxed_value::)) }; self.decode_result(r) } @@ -626,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}; @@ -642,7 +643,7 @@ mod test { db.create_scalar_function("half", 1, true, half).unwrap(); let result: Result = db.query_row("SELECT half(6)", &[], |r| r.get(0)); - assert_eq!(3f64, result.unwrap()); + assert!((3f64 - result.unwrap()).abs() < EPSILON); } #[test] @@ -650,7 +651,7 @@ mod test { let db = Connection::open_in_memory().unwrap(); db.create_scalar_function("half", 1, true, half).unwrap(); let result: Result = 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 = db.query_row("SELECT half(6)", &[], |r| r.get(0)); diff --git a/src/lib.rs b/src/lib.rs index 952ebe0..930e1c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,9 +53,6 @@ #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] -// Clippy complains about SQLite in our doc comments, but they're fine. -#![cfg_attr(feature="clippy", allow(doc_markdown))] - extern crate libc; extern crate libsqlite3_sys as ffi; #[macro_use] @@ -1224,10 +1221,9 @@ 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() { + 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()); } } diff --git a/src/transaction.rs b/src/transaction.rs index 5ef4909..e72add4 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -174,6 +174,7 @@ impl<'conn> Drop for Transaction<'conn> { } #[cfg(test)] +#[cfg_attr(feature="clippy", allow(similar_names))] mod test { use Connection; diff --git a/src/types.rs b/src/types.rs index aca7fad..4a92d72 100644 --- a/src/types.rs +++ b/src/types.rs @@ -328,11 +328,13 @@ impl FromSql for Value { } #[cfg(test)] +#[cfg_attr(feature="clippy", allow(similar_names))] mod test { 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(); @@ -403,6 +405,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 { @@ -426,7 +429,7 @@ mod test { assert_eq!(vec![1, 2], row.get_checked::>(0).unwrap()); assert_eq!("text", row.get_checked::(1).unwrap()); assert_eq!(1, row.get_checked::(2).unwrap()); - assert_eq!(1.5, row.get_checked::(3).unwrap()); + assert!((1.5 - row.get_checked::(3).unwrap()).abs() < EPSILON); assert!(row.get_checked::>(4).unwrap().is_none()); assert!(row.get_checked::>(4).unwrap().is_none()); assert!(row.get_checked::>(4).unwrap().is_none()); @@ -491,7 +494,10 @@ mod test { assert_eq!(Value::Text(String::from("text")), row.get_checked::(1).unwrap()); assert_eq!(Value::Integer(1), row.get_checked::(2).unwrap()); - assert_eq!(Value::Real(1.5), row.get_checked::(3).unwrap()); + match row.get_checked::(3).unwrap() { + Value::Real(val) => assert!((1.5 - val).abs() < EPSILON), + x => panic!("Invalid Value {:?}", x), + } assert_eq!(Value::Null, row.get_checked::(4).unwrap()); } } From 4a8a44d3d5b6ff3124689230c164ed26027d1e6e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:14:02 -0400 Subject: [PATCH 18/42] Add clippy to changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 63828da..36b0f3e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # Version UPCOMING (...) +* 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). From c0b8be99e8bcaa1d8abb451aa652989c95e52e3a Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:09:49 -0400 Subject: [PATCH 19/42] Fix crash due to 0-sized function type change in nightly --- src/trace.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/trace.rs b/src/trace.rs index bd3b537..a36aa33 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -35,7 +35,9 @@ pub unsafe fn config_log(callback: Option) -> Result<()> { 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(); From 43be3a84fa1ab245df448c084366c537d5b44553 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:15:41 -0400 Subject: [PATCH 20/42] Add bugfix to Changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 36b0f3e..6c401f0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # Version UPCOMING (...) +* 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. From 09dfa6afab2b66e79553aa12ead3e8c9b7d0988c Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:36:00 -0400 Subject: [PATCH 21/42] Move clippy allowance behind a cfg_attr --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index ebc79b7..cc753c7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -140,7 +140,7 @@ impl error::Error for Error { } } - #[allow(match_same_arms)] + #[cfg_attr(feature="clippy", allow(match_same_arms))] fn cause(&self) -> Option<&error::Error> { match *self { Error::SqliteFailure(ref err, _) => Some(err), From 5e780d7fc3edc823715ce738e5facac262282e63 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:36:46 -0400 Subject: [PATCH 22/42] Add supsuper to CONTRIBUTORS --- CONTRIBUTORS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6d0dd29..df22189 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -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) From 254a0b4cd84369261069a28c628fe13c17be3bc5 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 29 Mar 2016 16:37:22 -0400 Subject: [PATCH 23/42] Note load_extension change in Changelog --- Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog.md b/Changelog.md index 6c401f0..ad5ca25 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # Version UPCOMING (...) +* 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`. From 3769d085ae6fceff8622566035a056444de2c460 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 2 Apr 2016 13:57:55 +0200 Subject: [PATCH 24/42] Fix clippy warnings. --- src/types/chrono.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index dc1718f..22ac92a 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -272,39 +272,39 @@ mod test { #[test] fn test_naive_date() { let db = checked_memory_handle(); - let d = NaiveDate::from_ymd(2016, 2, 23); - db.execute("INSERT INTO foo (t) VALUES (?)", &[&d]).unwrap(); + let date = NaiveDate::from_ymd(2016, 2, 23); + db.execute("INSERT INTO foo (t) VALUES (?)", &[&date]).unwrap(); db.execute("UPDATE foo SET f = julianday(t)", &[]).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!(d, t); + assert_eq!(date, t); let f: NaiveDate = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(d, f); + assert_eq!(date, f); } #[test] fn test_naive_time() { let db = checked_memory_handle(); - let t = NaiveTime::from_hms(23, 56, 4); - db.execute("INSERT INTO foo (t) VALUES (?)", &[&t]).unwrap(); + 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!(t, v); + assert_eq!(time, v); } #[test] fn test_naive_date_time() { let db = checked_memory_handle(); - let d = NaiveDate::from_ymd(2016, 2, 23); - let t = NaiveTime::from_hms(23, 56, 4); - let dt = NaiveDateTime::new(d, t); + let date = NaiveDate::from_ymd(2016, 2, 23); + let time = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(date, time); - let di = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 3)); - let ds = NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 5)); + let di = NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 3)); + let ds = NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 5)); db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]).unwrap(); db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", @@ -327,15 +327,15 @@ mod test { db.execute("UPDATE foo set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); let b: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(NaiveDateTime::new(d, NaiveTime::from_hms(23, 56, 0)), b); + assert_eq!(NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 0)), b); } #[test] fn test_date_time_utc() { let db = checked_memory_handle(); - let d = NaiveDate::from_ymd(2016, 2, 23); - let t = NaiveTime::from_hms(23, 56, 4); - let dt = NaiveDateTime::new(d, t); + let date = NaiveDate::from_ymd(2016, 2, 23); + let time = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(date, time); let utc = UTC.from_utc_datetime(&dt); db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc]).unwrap(); @@ -354,9 +354,9 @@ mod test { #[test] fn test_date_time_local() { let db = checked_memory_handle(); - let d = NaiveDate::from_ymd(2016, 2, 23); - let t = NaiveTime::from_hms(23, 56, 4); - let dt = NaiveDateTime::new(d, t); + let date = NaiveDate::from_ymd(2016, 2, 23); + let time = NaiveTime::from_hms(23, 56, 4); + let dt = NaiveDateTime::new(date, time); let local = Local.from_local_datetime(&dt).single().unwrap(); db.execute("INSERT INTO foo (t) VALUES (?)", &[&local]).unwrap(); From 87844c688e92ddcbe539b651aec342ff6ca7fb9b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 2 Apr 2016 16:48:33 +0200 Subject: [PATCH 25/42] Fix clippy warning --- src/types/chrono.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 22ac92a..19f3372 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -322,12 +322,12 @@ mod test { assert_eq!(dt, i); db.execute("UPDATE foo set b = datetime(t)", &[]).unwrap(); // "YYYY-MM-DD HH:MM:SS" - let b: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(dt, b); + let hms: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(dt, hms); db.execute("UPDATE foo set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); - let b: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 0)), b); + let hm: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 0)), hm); } #[test] From 1cf68d2184bdf668d01e5bf21e6bdbe6dcdf17ff Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 15 Apr 2016 21:02:08 +0200 Subject: [PATCH 26/42] Julian/Unix times are ambiguous when converted to DateTime. --- src/types/chrono.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 19f3372..7f36ed7 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -237,13 +237,16 @@ impl FromSql for DateTime { Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), } } else { + // TODO from_utc_datetime versus from from_local_datetime NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) } } ffi::SQLITE_INTEGER => { + // TODO from_utc_datetime versus from from_local_datetime NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) } ffi::SQLITE_FLOAT => { + // TODO from_utc_datetime versus from from_local_datetime NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) } _ => Err(Error::InvalidColumnType), From 79376a4ca96f4c645c3fe0aabff12525eff83c0e Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 4 May 2016 21:57:16 +0200 Subject: [PATCH 27/42] Partially fix the bug reported by @iwinux. If microseconds are specified but no timezone indicator, parsing fails... --- src/types/chrono.rs | 73 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 19f3372..41871d2 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -179,15 +179,14 @@ impl FromSql for DateTime { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { match sqlite3_column_type(stmt, col) { ffi::SQLITE_TEXT => { - let s = try!(String::column_result(stmt, col)); + let mut s = try!(String::column_result(stmt, col)); if s.len() > 23 { - let fmt = if s.as_bytes()[10] == b'T' { - "%Y-%m-%dT%H:%M:%S%.f%:z" - } else { - "%Y-%m-%d %H:%M:%S%.f%:z" + if s.as_bytes()[10] == b' ' { + s.as_mut_vec()[10] = b'T'; }; - match UTC.datetime_from_str(&s, fmt) { - Ok(dt) => Ok(dt), + // It cannot be used when the offset can be missing. + match DateTime::parse_from_rfc3339(&s) { + Ok(dt) => Ok(dt.with_timezone(&UTC)), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), } } else { @@ -220,20 +219,19 @@ impl ToSql for DateTime { /// "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 date and time with time zone. /// ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) -/// When the timezone is not specified, Local is used. +/// When the timezone is not specified, UTC is used. impl FromSql for DateTime { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { match sqlite3_column_type(stmt, col) { ffi::SQLITE_TEXT => { - let s = try!(String::column_result(stmt, col)); + let mut s = try!(String::column_result(stmt, col)); if s.len() > 23 { - let fmt = if s.as_bytes()[10] == b'T' { - "%Y-%m-%dT%H:%M:%S%.f%:z" - } else { - "%Y-%m-%d %H:%M:%S%.f%:z" + if s.as_bytes()[10] == b' ' { + s.as_mut_vec()[10] = b'T'; }; - match Local.datetime_from_str(&s, fmt) { - Ok(dt) => Ok(dt), + // It cannot be used when the offset can be missing. + match DateTime::parse_from_rfc3339(&s) { + Ok(dt) => Ok(dt.with_timezone(&Local)), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), } } else { @@ -371,4 +369,49 @@ mod test { let i: DateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(local, i); } + + #[test] + fn test_timezone() { + // Worst + UTC.datetime_from_str("2016-05-04 17:00:42.862471+08:00", + "%Y-%m-%d %H:%M:%S%.f%:z") + .unwrap_err(); + UTC.datetime_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%:z") + .unwrap_err(); + UTC.datetime_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%:z").unwrap_err(); + + UTC.datetime_from_str("2016-05-04 17:00:42.862471+08:00", "%Y-%m-%d %H:%M:%S%.f%z") + .unwrap_err(); + UTC.datetime_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%z").unwrap_err(); + UTC.datetime_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%z").unwrap_err(); + + // Better but... + DateTime::parse_from_str("2016-05-04 17:00:42.862471+08:00", "%Y-%m-%d %H:%M:%S%.f%z") + .unwrap() + .with_timezone(&UTC); + DateTime::parse_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%z") + .unwrap_err(); // Invalid + DateTime::parse_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%z") + .unwrap_err(); // TooShort + + DateTime::parse_from_str("2016-05-04 17:00:42.862471+08:00", + "%Y-%m-%d %H:%M:%S%.f%:z") + .unwrap() + .with_timezone(&UTC); + DateTime::parse_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%:z") + .unwrap_err(); // Invalid + DateTime::parse_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%:z") + .unwrap_err(); // TooShort + + // Best but in SQLite, the timezone indicator is optional + DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471+08:00") + .unwrap() + .with_timezone(&UTC); + DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471Z").unwrap().with_timezone(&UTC); + DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471").unwrap_err(); // TooShort + + "2016-05-04T17:00:42.862471+08:00".parse::>().unwrap(); + "2016-05-04T17:00:42.862471Z".parse::>().unwrap(); + "2016-05-04T17:00:42.862471".parse::>().unwrap_err(); // TooShort + } } From b20168fe9cb16217fcea2a3da04fe0f13722097f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 May 2016 12:08:57 +0200 Subject: [PATCH 28/42] Use String::from_utf8_lossy for error/trace. Try to use the original message even if there are invalid characters. --- src/lib.rs | 3 +-- src/trace.rs | 21 +++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 930e1c6..466ac78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,8 +101,7 @@ pub type Result = result::Result; 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_owned() + String::from_utf8_lossy(c_slice).into_owned() } fn str_to_cstring(s: &str) -> Result { diff --git a/src/trace.rs b/src/trace.rs index a36aa33..7dd1417 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -27,9 +27,8 @@ pub unsafe fn config_log(callback: Option) -> 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 { @@ -70,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(); @@ -96,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(); From 350dc59bb964095c7040b39e58a4c5b1d5d5b2a4 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 15:32:59 -0400 Subject: [PATCH 29/42] Remove unnecessary column_has_valid_sqlite_type impls --- src/types/chrono.rs | 16 ---------------- src/types/mod.rs | 4 ---- src/types/time.rs | 4 ---- 3 files changed, 24 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 41871d2..bd23f34 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -54,10 +54,6 @@ impl FromSql for NaiveDate { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } /// ISO 8601 time without timezone => "HH:MM:SS.SSS" @@ -158,10 +154,6 @@ impl FromSql for NaiveDateTime { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } /// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" @@ -202,10 +194,6 @@ impl FromSql for DateTime { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } @@ -247,10 +235,6 @@ impl FromSql for DateTime { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } // struct UnixTime(NaiveDateTime); diff --git a/src/types/mod.rs b/src/types/mod.rs index 4cffd50..46a5c54 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -298,10 +298,6 @@ impl FromSql for Value { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true - } } #[cfg(test)] diff --git a/src/types/time.rs b/src/types/time.rs index 30886a4..2fe19c5 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -42,10 +42,6 @@ impl FromSql for time::Timespec { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } #[cfg(test)] From 42bc173009082eb403089970b72a4a26ad3e5173 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 19:56:57 -0500 Subject: [PATCH 30/42] Only use strings for NaiveDate's ToSql/FromSql impl --- src/types/chrono.rs | 41 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index bd23f34..023dfed 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -14,7 +14,6 @@ use ffi::sqlite3_column_type; const JULIAN_DAY: f64 = 2440587.5; // 1970-01-01 00:00:00 is JD 2440587.5 const DAY_IN_SECONDS: f64 = 86400.0; -const JULIAN_DAY_GREGORIAN: f64 = 1721424.5; // Jan 1, 1 proleptic Gregorian calendar /// ISO 8601 calendar date without timezone => "YYYY-MM-DD" impl ToSql for NaiveDate { @@ -24,36 +23,19 @@ impl ToSql for NaiveDate { } } -/// "YYYY-MM-DD" or Julian Day => ISO 8601 calendar date without timezone. +/// "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 { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - 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))), - } - } - ffi::SQLITE_FLOAT => { - // if column affinity is REAL and an integer/unix timestamp is inserted => unexpected result - let mut jd = ffi::sqlite3_column_double(stmt, col); - jd -= JULIAN_DAY_GREGORIAN; - if jd < i32::min_value() as f64 || jd > i32::max_value() as f64 { - let err: Box = "out-of-range date".into(); - return Err(Error::FromSqlConversionFailure(err)); - } - match NaiveDate::from_num_days_from_ce_opt(jd as i32) { - Some(dt) => Ok(dt), - None => { - let err: Box = "out-of-range date".into(); - Err(Error::FromSqlConversionFailure(err)) - } - } - } - _ => Err(Error::InvalidColumnType), + 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" @@ -80,7 +62,7 @@ impl FromSql for NaiveTime { } unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == ffi::SQLITE_TEXT + String::column_has_valid_sqlite_type(stmt, col) } } @@ -256,14 +238,11 @@ mod test { let db = checked_memory_handle(); let date = NaiveDate::from_ymd(2016, 2, 23); db.execute("INSERT INTO foo (t) VALUES (?)", &[&date]).unwrap(); - db.execute("UPDATE foo SET f = julianday(t)", &[]).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); - let f: NaiveDate = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(date, f); } #[test] From 07801ca3709d6baa92155bb1ff2c12de1d548db7 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:23:02 -0500 Subject: [PATCH 31/42] Greatly reduce implementation of chrono types. Limit ToSql/FromSql to string representations, and limit allowed string representations to RFC3339 (if a timezone is expected) and SQLite's `datetime()` format (if not). --- src/types/chrono.rs | 254 ++++++++------------------------------------ 1 file changed, 44 insertions(+), 210 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 023dfed..98ed77e 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -1,19 +1,13 @@ //! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. extern crate chrono; -use std::error; use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local}; use libc::c_int; use {Error, Result}; use types::{FromSql, ToSql}; -use ffi; use ffi::sqlite3_stmt; -use ffi::sqlite3_column_type; - -const JULIAN_DAY: f64 = 2440587.5; // 1970-01-01 00:00:00 is JD 2440587.5 -const DAY_IN_SECONDS: f64 = 86400.0; /// ISO 8601 calendar date without timezone => "YYYY-MM-DD" impl ToSql for NaiveDate { @@ -69,159 +63,68 @@ impl FromSql for NaiveTime { /// 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-%d %H:%M:%S%.f").to_string(); + 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"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 combined date and time without timezone. -/// ("YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) +/// "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 { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - let s = try!(String::column_result(stmt, col)); - let fmt = match s.len() { - 16 => { - match s.as_bytes()[10] { - b'T' => "%Y-%m-%dT%H:%M", - _ => "%Y-%m-%d %H:%M", - } - } - 19 => { - match s.as_bytes()[10] { - b'T' => "%Y-%m-%dT%H:%M:%S", - _ => "%Y-%m-%d %H:%M:%S", - } - } - _ => { - match s.as_bytes()[10] { - b'T' => "%Y-%m-%dT%H:%M:%S%.f", - _ => "%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))), - } - } - ffi::SQLITE_INTEGER => { - match NaiveDateTime::from_timestamp_opt(ffi::sqlite3_column_int64(stmt, col), 0) { - Some(dt) => Ok(dt), - None => { - let err: Box = "out-of-range number of seconds" - .into(); - Err(Error::FromSqlConversionFailure(err)) - } - } - } - ffi::SQLITE_FLOAT => { - // if column affinity is REAL and an integer/unix timestamp is inserted => unexpected result - let mut jd = ffi::sqlite3_column_double(stmt, col); - jd -= JULIAN_DAY; - jd *= DAY_IN_SECONDS; - let ns = jd.fract() * 10f64.powi(9); - match NaiveDateTime::from_timestamp_opt(jd as i64, ns as u32) { - Some(dt) => Ok(dt), - None => { - let err: Box = "out-of-range number of \ - seconds and/or invalid \ - nanosecond" - .into(); - Err(Error::FromSqlConversionFailure(err)) - } - } - } - _ => Err(Error::InvalidColumnType), + 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))), } } -} -/// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" -impl ToSql for DateTime { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - let date_str = self.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); - date_str.bind_parameter(stmt, col) + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + String::column_has_valid_sqlite_type(stmt, col) } } -/// "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 date and time with time zone. -/// ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) -/// When the timezone is not specified, UTC is used. +/// Date and time with time zone => RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"). +impl ToSql for DateTime where Tz::Offset: ::std::fmt::Display { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + self.to_rfc3339().bind_parameter(stmt, col) + } +} + +/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - let mut s = try!(String::column_result(stmt, col)); - if s.len() > 23 { - if s.as_bytes()[10] == b' ' { - s.as_mut_vec()[10] = b'T'; - }; - // It cannot be used when the offset can be missing. - match DateTime::parse_from_rfc3339(&s) { - Ok(dt) => Ok(dt.with_timezone(&UTC)), - Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } - } else { - NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) - } - } - ffi::SQLITE_INTEGER => { - NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) - } - ffi::SQLITE_FLOAT => { - NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)) - } - _ => Err(Error::InvalidColumnType), + let s = try!(String::column_result(stmt, col)); + 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)), } } -} - -/// ISO 8601 date and time with time zone => "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM" -impl ToSql for DateTime { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - let date_str = self.format("%Y-%m-%d %H:%M:%S%.f%:z").to_string(); - date_str.bind_parameter(stmt, col) + unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { + String::column_has_valid_sqlite_type(stmt, col) } } -/// "YYYY-MM-DD HH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DD HH:MM"/"YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS"/ Julian Day / Unix Time => ISO 8601 date and time with time zone. -/// ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"/"YYYY-MM-DDTHH:MM"/"YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS" also supported) -/// When the timezone is not specified, UTC is used. +/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - let mut s = try!(String::column_result(stmt, col)); - if s.len() > 23 { - if s.as_bytes()[10] == b' ' { - s.as_mut_vec()[10] = b'T'; - }; - // It cannot be used when the offset can be missing. - match DateTime::parse_from_rfc3339(&s) { - Ok(dt) => Ok(dt.with_timezone(&Local)), - Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } - } else { - NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) - } - } - ffi::SQLITE_INTEGER => { - NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) - } - ffi::SQLITE_FLOAT => { - NaiveDateTime::column_result(stmt, col).map(|dt| Local.from_utc_datetime(&dt)) - } - _ => Err(Error::InvalidColumnType), - } + let utc_dt = try!(DateTime::::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::::column_has_valid_sqlite_type(stmt, col) } } -// struct UnixTime(NaiveDateTime); -// struct JulianTime(NaiveDateTime) - #[cfg(test)] mod test { use Connection; @@ -264,117 +167,48 @@ mod test { let time = NaiveTime::from_hms(23, 56, 4); let dt = NaiveDateTime::new(date, time); - let di = NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 3)); - let ds = NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 5)); - db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt]).unwrap(); - db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", - &[]) - .unwrap(); let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!("2016-02-23 23:56:04", s); + 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); - let f: NaiveDateTime = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); - // `2016-02-23T23:56:04` vs `2016-02-23T23:56:03.999992609` - assert!(f.ge(&di) && f.le(&ds)); - let i: NaiveDateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(dt, i); 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); - - db.execute("UPDATE foo set b = strftime('%Y-%m-%dT%H:%M', t)", &[]).unwrap(); - let hm: NaiveDateTime = db.query_row("SELECT b FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(NaiveDateTime::new(date, NaiveTime::from_hms(23, 56, 0)), hm); } #[test] fn test_date_time_utc() { let db = checked_memory_handle(); let date = NaiveDate::from_ymd(2016, 2, 23); - let time = NaiveTime::from_hms(23, 56, 4); + 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(); - db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", - &[]) - .unwrap(); let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!("2016-02-23 23:56:04+00:00", s); + assert_eq!("2016-02-23T23:56:04.789+00:00", s); let v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(utc, v); - let i: DateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(utc, i); } #[test] fn test_date_time_local() { let db = checked_memory_handle(); let date = NaiveDate::from_ymd(2016, 2, 23); - let time = NaiveTime::from_hms(23, 56, 4); + 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(); - db.execute("UPDATE foo SET f = julianday(t), i = strftime('%s', t)", - &[]) - .unwrap(); - // let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); - // assert_eq!("2016-02-23 23:56:04.000+01:00", s); + let s: String = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + let offset = Local.offset_from_utc_datetime(&dt); + assert_eq!(format!("2016-02-23T23:56:04.789{:}", offset), s); let v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(local, v); - let i: DateTime = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(local, i); - } - - #[test] - fn test_timezone() { - // Worst - UTC.datetime_from_str("2016-05-04 17:00:42.862471+08:00", - "%Y-%m-%d %H:%M:%S%.f%:z") - .unwrap_err(); - UTC.datetime_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%:z") - .unwrap_err(); - UTC.datetime_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%:z").unwrap_err(); - - UTC.datetime_from_str("2016-05-04 17:00:42.862471+08:00", "%Y-%m-%d %H:%M:%S%.f%z") - .unwrap_err(); - UTC.datetime_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%z").unwrap_err(); - UTC.datetime_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%z").unwrap_err(); - - // Better but... - DateTime::parse_from_str("2016-05-04 17:00:42.862471+08:00", "%Y-%m-%d %H:%M:%S%.f%z") - .unwrap() - .with_timezone(&UTC); - DateTime::parse_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%z") - .unwrap_err(); // Invalid - DateTime::parse_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%z") - .unwrap_err(); // TooShort - - DateTime::parse_from_str("2016-05-04 17:00:42.862471+08:00", - "%Y-%m-%d %H:%M:%S%.f%:z") - .unwrap() - .with_timezone(&UTC); - DateTime::parse_from_str("2016-05-04 17:00:42.862471Z", "%Y-%m-%d %H:%M:%S%.f%:z") - .unwrap_err(); // Invalid - DateTime::parse_from_str("2016-05-04 17:00:42.862471", "%Y-%m-%d %H:%M:%S%.f%:z") - .unwrap_err(); // TooShort - - // Best but in SQLite, the timezone indicator is optional - DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471+08:00") - .unwrap() - .with_timezone(&UTC); - DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471Z").unwrap().with_timezone(&UTC); - DateTime::parse_from_rfc3339("2016-05-04T17:00:42.862471").unwrap_err(); // TooShort - - "2016-05-04T17:00:42.862471+08:00".parse::>().unwrap(); - "2016-05-04T17:00:42.862471Z".parse::>().unwrap(); - "2016-05-04T17:00:42.862471".parse::>().unwrap_err(); // TooShort } } From 88fb175b40e529ad31c8878d139e198e2c9edcf4 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:30:11 -0500 Subject: [PATCH 32/42] Limit Timespec representation to strings. --- src/types/time.rs | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/src/types/time.rs b/src/types/time.rs index 2fe19c5..fe77956 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -4,13 +4,9 @@ use libc::c_int; use {Error, Result}; use types::{FromSql, ToSql}; -use ffi; use ffi::sqlite3_stmt; -use ffi::sqlite3_column_type; const SQLITE_DATETIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; -const JULIAN_DAY: f64 = 2440587.5; // 1970-01-01 00:00:00 is JD 2440587.5 -const DAY_IN_SECONDS: f64 = 86400.0; impl ToSql for time::Timespec { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { @@ -21,27 +17,16 @@ impl ToSql for time::Timespec { impl FromSql for time::Timespec { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - 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))), - } - }) - } - ffi::SQLITE_INTEGER => Ok(time::Timespec::new(ffi::sqlite3_column_int64(stmt, col), 0)), - ffi::SQLITE_FLOAT => { - let mut jd = ffi::sqlite3_column_double(stmt, col); - jd -= JULIAN_DAY; - jd *= DAY_IN_SECONDS; - let ns = jd.fract() * 10f64.powi(9); - Ok(time::Timespec::new(jd as i64, ns as i32)) - } - _ => Err(Error::InvalidColumnType), + 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)] @@ -63,15 +48,9 @@ mod test { sec: 10_000, nsec: 0, }; - db.execute("INSERT INTO foo(t, i) VALUES (?, ?)", &[&ts, &ts.sec]).unwrap(); - db.execute("UPDATE foo SET f = julianday(t)", &[]).unwrap(); + 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); - let from: time::Timespec = db.query_row("SELECT i FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(from, ts); - // `Timespec { sec: 9999, nsec: 999994039 }` vs `Timespec{ sec: 10000, nsec: 0 }` - let from: time::Timespec = db.query_row("SELECT f FROM foo", &[], |r| r.get(0)).unwrap(); - assert!((from.sec - ts.sec).abs() <= 1); } } From aa2b3b26bd11492572a9d63369df8e6cd3300333 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:39:18 -0500 Subject: [PATCH 33/42] Add chrono note to Changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index ad5ca25..e000549 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # Version UPCOMING (...) +* 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. From 5038e2a7051f242b21312ef8709c751dea3acd65 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:46:50 -0500 Subject: [PATCH 34/42] Remove unnecessary column_has_valid_sqlite_type impl --- src/types/serde_json.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index 251e08a..22eebb3 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -40,10 +40,6 @@ impl FromSql for Value { _ => Err(Error::InvalidColumnType), } } - - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true // to avoid double check - } } #[cfg(test)] From b87d4b44a633beb0c46bc12ab154f60f51e3c127 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:51:04 -0500 Subject: [PATCH 35/42] Minor code cleanup/refactoring. No functional changes. --- src/types/serde_json.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index 22eebb3..3c2089a 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -22,23 +22,18 @@ impl ToSql for Value { /// Deserialize text/blob to JSON `Value`. impl FromSql for Value { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - match sqlite3_column_type(stmt, col) { + let value_result = match sqlite3_column_type(stmt, col) { ffi::SQLITE_TEXT => { let s = try!(String::column_result(stmt, col)); - match serde_json::from_str(&s) { - Ok(v) => Ok(v), - Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } + serde_json::from_str(&s) } ffi::SQLITE_BLOB => { - let blob: Vec = try!(FromSql::column_result(stmt, col)); - match serde_json::from_slice(&blob[..]) { - Ok(v) => Ok(v), - Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } + let blob = try!(Vec::::column_result(stmt, col)); + serde_json::from_slice(&blob) } - _ => Err(Error::InvalidColumnType), - } + _ => return Err(Error::InvalidColumnType) + }; + value_result.map_err(|err| { Error::FromSqlConversionFailure(Box::new(err)) }) } } @@ -57,7 +52,7 @@ mod test { fn test_json_value() { let db = checked_memory_handle(); - let json = "{\"foo\": 13, \"bar\": \"baz\"}"; + 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()]) From 6aa77e42ca134b5d516d43a4e96ee8fab24c154e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:52:09 -0500 Subject: [PATCH 36/42] Add serde_json note to Changelog. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index e000549..9e6729a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,6 @@ # 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. From 34d5e2db2467cd81fbddaff8ea8996a2f93b8adc Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 09:08:31 -0500 Subject: [PATCH 37/42] Always store DateTimes in UTC --- src/types/chrono.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 98ed77e..dd63c40 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -91,10 +91,11 @@ impl FromSql for NaiveDateTime { } } -/// Date and time with time zone => RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM"). -impl ToSql for DateTime where Tz::Offset: ::std::fmt::Display { +/// Date and time with time zone => UTC RFC3339 timestamp ("YYYY-MM-DDTHH:MM:SS.SSS+00:00"). +impl ToSql for DateTime { unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - self.to_rfc3339().bind_parameter(stmt, col) + let utc_dt = self.with_timezone(&UTC); + utc_dt.to_rfc3339().bind_parameter(stmt, col) } } @@ -205,9 +206,10 @@ mod test { 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(); - let offset = Local.offset_from_utc_datetime(&dt); - assert_eq!(format!("2016-02-23T23:56:04.789{:}", offset), s); + assert!(s.ends_with("+00:00")); + let v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(local, v); } From 6d9b268776bb87f9057139c4869b741a1a6999cd Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 09:13:45 -0500 Subject: [PATCH 38/42] Add tests confirming DateTime works with " " seperator instead of "T" --- src/types/chrono.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index dd63c40..39a7093 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -129,7 +129,7 @@ impl FromSql for DateTime { #[cfg(test)] mod test { use Connection; - use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC}; + use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC, Duration}; fn checked_memory_handle() -> Connection { let db = Connection::open_in_memory().unwrap(); @@ -192,8 +192,15 @@ mod test { 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 v: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); - assert_eq!(utc, v); + + let v1: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc, v1); + + let v2: DateTime = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc, v2); + + let v3: DateTime = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc - Duration::milliseconds(789), v3); } #[test] From 6a4abff462ff8442a682400831ffa6654a6208d9 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 09:27:50 -0500 Subject: [PATCH 39/42] Restore support for full RFC3339 timestamps with a space seperator --- src/types/chrono.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 39a7093..4aaa63d 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -102,7 +102,16 @@ impl ToSql for DateTime { /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - let s = try!(String::column_result(stmt, col)); + 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)), @@ -201,6 +210,9 @@ mod test { let v3: DateTime = 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 = db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)).unwrap(); + assert_eq!(utc, v4); } #[test] From 4924c0b38b134b498ec12f0e58f9eb115837a2dd Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 10:02:07 -0500 Subject: [PATCH 40/42] rustfmt --- src/types/chrono.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 4aaa63d..a9e0bea 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -68,8 +68,8 @@ impl ToSql for NaiveDateTime { } } -/// "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) +/// "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 { let s = try!(String::column_result(stmt, col)); @@ -138,7 +138,8 @@ impl FromSql for DateTime { #[cfg(test)] mod test { use Connection; - use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC, Duration}; + use super::chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, UTC, + Duration}; fn checked_memory_handle() -> Connection { let db = Connection::open_in_memory().unwrap(); @@ -205,13 +206,16 @@ mod test { let v1: DateTime = db.query_row("SELECT t FROM foo", &[], |r| r.get(0)).unwrap(); assert_eq!(utc, v1); - let v2: DateTime = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0)).unwrap(); + let v2: DateTime = db.query_row("SELECT '2016-02-23 23:56:04.789'", &[], |r| r.get(0)) + .unwrap(); assert_eq!(utc, v2); - let v3: DateTime = db.query_row("SELECT '2016-02-23 23:56:04'", &[], |r| r.get(0)).unwrap(); + let v3: DateTime = 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 = db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)).unwrap(); + let v4: DateTime = + db.query_row("SELECT '2016-02-23 23:56:04.789+00:00'", &[], |r| r.get(0)).unwrap(); assert_eq!(utc, v4); } From 24024b90b64c45fc4e12c91353ad7980fadc57d6 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 10:15:05 -0500 Subject: [PATCH 41/42] Add serde_json feature to CI checks --- .travis.yml | 3 ++- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0d5eb5..c96f8af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,5 @@ script: - cargo test --features trace - cargo test --features functions - cargo test --features chrono - - cargo test --features "backup blob chrono functions load_extension trace" + - cargo test --features serde_json + - cargo test --features "backup blob chrono functions load_extension serde_json trace" diff --git a/appveyor.yml b/appveyor.yml index 0d9a5f3..9b0258c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ build: false test_script: - cargo test --lib --verbose - - cargo test --lib --features "backup blob chrono functions load_extension trace" + - cargo test --lib --features "backup blob chrono functions load_extension serde_json trace" cache: - C:\Users\appveyor\.cargo From 81249538826f226a03764a4ae512cd4b57df6d1f Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 10:17:25 -0500 Subject: [PATCH 42/42] rustfmt --- src/types/serde_json.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index 3c2089a..dc1eeff 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -31,9 +31,9 @@ impl FromSql for Value { let blob = try!(Vec::::column_result(stmt, col)); serde_json::from_slice(&blob) } - _ => return Err(Error::InvalidColumnType) + _ => return Err(Error::InvalidColumnType), }; - value_result.map_err(|err| { Error::FromSqlConversionFailure(Box::new(err)) }) + value_result.map_err(|err| Error::FromSqlConversionFailure(Box::new(err))) } } @@ -55,8 +55,8 @@ mod test { 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(); + &[&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);