From 5d8a840b5dc9114030bcfe44c29eb0b0e64697cb Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 24 Dec 2017 09:02:40 +0000 Subject: [PATCH 1/9] Fix date/time format for SQLite, use RFC 3339 We implement `ToSql` and `FromSql` for `time::Timespec` values. Our documentation indicates that we store the value in the same format used by SQLite's built-in date/time functions, but this was not correct. We were using the format: %Y-%m-%d %H:%M:%S:%f %Z This format cannot be interpreted at all by SQLite's built-in date/time functions. There are three reasons for this: - SQLite supports only two timezone formats: `[+-]HH:MM` and the literal character `Z` (indicating UTC) - SQLite does not support a space before the timezone indicator - SQLite supports a period (`.`) between the seconds field and the fractional seconds field, but not a colon (`:`) SQLite does support the RFC 3339 date/time format, which is standard in many other places. As we're always storing a UTC value, we'll simply use a trailing `Z` to indicate the timezone, as allowed by RFC 3339. The new format is: %Y-%m-%dT%H:%M:%S.%fZ To avoid breaking applications using databases with values in the old format, we'll continue to support it as a fallback for `FromSql`. [1] https://www.sqlite.org/lang_datefunc.html [2] https://tools.ietf.org/html/rfc3339 --- src/types/mod.rs | 13 +++++++------ src/types/time.rs | 13 ++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index c998d89..74757cb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -10,12 +10,13 @@ //! * Strings (`String` and `&str`) //! * Blobs (`Vec` and `&[u8]`) //! -//! Additionally, because it is such a common data type, implementations are provided for -//! `time::Timespec` that use a string for storage (using the same format string, -//! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin -//! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage -//! truncates timespecs to the nearest second. If you want different storage for timespecs, you can -//! use a newtype. For example, to store timespecs as `f64`s: +//! Additionally, because it is such a common data type, implementations are +//! provided for `time::Timespec` that use the RFC 3339 date/time format, +//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values +//! can be parsed by SQLite's builtin +//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you +//! want different storage for timespecs, you can use a newtype. For example, to +//! store timespecs as `f64`s: //! //! ```rust //! extern crate rusqlite; diff --git a/src/types/time.rs b/src/types/time.rs index f0dd46c..458435c 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -3,7 +3,8 @@ extern crate time; use Result; use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; -const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; +const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ"; +const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; impl ToSql for time::Timespec { fn to_sql(&self) -> Result { @@ -19,10 +20,12 @@ impl FromSql for time::Timespec { fn column_result(value: ValueRef) -> FromSqlResult { value .as_str() - .and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) { - Ok(tm) => Ok(tm.to_timespec()), - Err(err) => Err(FromSqlError::Other(Box::new(err))), - }) + .and_then(|s| { + time::strptime(s, SQLITE_DATETIME_FMT) + .or_else(|err| { + time::strptime(s, SQLITE_DATETIME_FMT_LEGACY) + .or(Err(FromSqlError::Other(Box::new(err))))})}) + .map(|tm| tm.to_timespec()) } } From 2a03c1ad4d967ae35f0d9edbc4965e082b8755f1 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Thu, 11 Jan 2018 16:52:32 -0800 Subject: [PATCH 2/9] Add DropBehavior::Panic to enforce intentional commit or rollback. --- src/transaction.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/transaction.rs b/src/transaction.rs index 41414f7..ac03b24 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -26,6 +26,9 @@ pub enum DropBehavior { /// Do not commit or roll back changes - this will leave the transaction or savepoint /// open, so should be used with care. Ignore, + + /// Panic. Used to enforce intentional behavior during development. + Panic, } /// Old name for `Transaction`. `SqliteTransaction` is deprecated. @@ -192,6 +195,7 @@ impl<'conn> Transaction<'conn> { DropBehavior::Commit => self.commit_(), DropBehavior::Rollback => self.rollback_(), DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), } } } @@ -303,6 +307,7 @@ impl<'conn> Savepoint<'conn> { DropBehavior::Commit => self.commit_(), DropBehavior::Rollback => self.rollback(), DropBehavior::Ignore => Ok(()), + DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), } } } From 96e5cf2239fd6d13c875bcfbe081a684dab4e746 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Feb 2018 10:11:39 +0100 Subject: [PATCH 3/9] Fix statement cache SQLite ignores the SQL tail (whitespaces, comments). We can easily ignore whitespaces by trimming the SQL. --- src/cache.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index bfafbac..d5bb5b4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -46,6 +46,7 @@ impl Connection { self.cache.set_capacity(capacity) } + /// Remove/finalize all prepared statements currently in the cache. pub fn flush_prepared_statement_cache(&self) { self.cache.flush() } @@ -124,7 +125,7 @@ impl StatementCache { sql: &str) -> Result> { let mut cache = self.0.borrow_mut(); - let stmt = match cache.remove(sql) { + let stmt = match cache.remove(sql.trim()) { Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)), None => conn.prepare(sql), }; @@ -135,7 +136,7 @@ impl StatementCache { fn cache_stmt(&self, stmt: RawStatement) { let mut cache = self.0.borrow_mut(); stmt.clear_bindings(); - let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string(); + let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).trim().to_string(); cache.insert(sql, stmt); } @@ -285,4 +286,27 @@ mod test { conn.close().expect("connection not closed"); } + + #[test] + fn test_cache_key() { + let db = Connection::open_in_memory().unwrap(); + let cache = &db.cache; + assert_eq!(0, cache.len()); + + //let sql = " PRAGMA schema_version; -- comment"; + let sql = "PRAGMA schema_version; "; + { + let mut stmt = db.prepare_cached(sql).unwrap(); + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); + } + assert_eq!(1, cache.len()); + + { + let mut stmt = db.prepare_cached(sql).unwrap(); + assert_eq!(0, cache.len()); + assert_eq!(0, stmt.query_row(&[], |r| r.get::(0)).unwrap()); + } + assert_eq!(1, cache.len()); + } } From 83c8db2d21d9be3aeb108e163de17087468e4747 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Mar 2018 10:58:42 +0100 Subject: [PATCH 4/9] Fix Connection::open documentation (#332) --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0ccd22f..98e62da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ impl Connection { /// Open a new connection to a SQLite database. /// /// `Connection::open(path)` is equivalent to `Connection::open_with_flags(path, - /// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`. + /// OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE)`. /// /// # Failure /// From 50d379b564da9586ea0f93e3d80658acf3464159 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 20:07:46 +0200 Subject: [PATCH 5/9] Make Statement::column_index case insensitive Fix #330 --- src/statement.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/statement.rs b/src/statement.rs index 2674c32..09c3bc3 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -45,7 +45,7 @@ impl<'conn> Statement<'conn> { let bytes = name.as_bytes(); let n = self.column_count(); for i in 0..n { - if bytes == self.stmt.column_name(i).to_bytes() { + if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { return Ok(i); } } @@ -785,4 +785,30 @@ mod test { let y: Result = stmt.query_row(&[&1i32], |r| r.get(0)); assert_eq!(3i64, y.unwrap()); } + + #[test] + fn test_query_by_column_name() { + let db = Connection::open_in_memory().unwrap(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql).unwrap(); + let mut stmt = db.prepare("SELECT y FROM foo").unwrap(); + let y: Result = stmt.query_row(&[], |r| r.get("y")); + assert_eq!(3i64, y.unwrap()); + } + + #[test] + fn test_query_by_column_name_ignore_case() { + let db = Connection::open_in_memory().unwrap(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y INTEGER); + INSERT INTO foo VALUES(1, 3); + END;"; + db.execute_batch(sql).unwrap(); + let mut stmt = db.prepare("SELECT y as Y FROM foo").unwrap(); + let y: Result = stmt.query_row(&[], |r| r.get("y")); + assert_eq!(3i64, y.unwrap()); + } } From 7f193c03f1131a32b76d304cc8286d33181ea09c Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 27 Mar 2018 20:40:41 +0200 Subject: [PATCH 6/9] Upgrade stable version used by appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3b87004..155f6e7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: matrix: - - TARGET: 1.21.0-x86_64-pc-windows-gnu + - TARGET: 1.24.1-x86_64-pc-windows-gnu MSYS2_BITS: 64 - - TARGET: 1.21.0-x86_64-pc-windows-msvc + - TARGET: 1.24.1-x86_64-pc-windows-msvc VCPKG_DEFAULT_TRIPLET: x64-windows VCPKGRS_DYNAMIC: 1 - TARGET: nightly-x86_64-pc-windows-msvc From 7c4105afd9569fc86a45186573dc304519748eac Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 6 Apr 2018 21:37:03 +0200 Subject: [PATCH 7/9] Upgrade dependencies bindgen from 0.32 to 0.35 lazy_static from 0.2 to 1.0 --- Cargo.toml | 2 +- libsqlite3-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..1c55343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" -lazy_static = "0.2" +lazy_static = "1.0" regex = "0.2" [dependencies.libsqlite3-sys] diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..1127b7c 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -23,7 +23,7 @@ min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] [build-dependencies] -bindgen = { version = "0.32", optional = true } +bindgen = { version = "0.35", optional = true } pkg-config = { version = "0.3", optional = true } cc = { version = "1.0", optional = true } From cd26d53a23d895811c8b70a3a7ebcae75ccc51bf Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 27 Apr 2018 19:06:34 +0200 Subject: [PATCH 8/9] Upgrade dependencies bindgen 0.36 lazy_static 1.0 --- Cargo.toml | 2 +- libsqlite3-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5199d0e..1c55343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" -lazy_static = "0.2" +lazy_static = "1.0" regex = "0.2" [dependencies.libsqlite3-sys] diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 32d4d44..64e3f1e 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -23,7 +23,7 @@ min_sqlite_version_3_7_4 = ["pkg-config", "vcpkg"] min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"] [build-dependencies] -bindgen = { version = "0.32", optional = true } +bindgen = { version = "0.36", optional = true } pkg-config = { version = "0.3", optional = true } cc = { version = "1.0", optional = true } From 9c36d29f5e21a5d2091bbc1785e729afd5b7f194 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 2 May 2018 18:21:35 +0200 Subject: [PATCH 9/9] Upgrade regexp dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1c55343..fdf88bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ serde_json = { version = "1.0", optional = true } [dev-dependencies] tempdir = "0.3" lazy_static = "1.0" -regex = "0.2" +regex = "1.0" [dependencies.libsqlite3-sys] path = "libsqlite3-sys"