From 3c1ce6428a7ef9d1b05a2bd4d13f30a0c8eeb742 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Feb 2016 19:06:37 +0100 Subject: [PATCH 1/7] 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 1cf68d2184bdf668d01e5bf21e6bdbe6dcdf17ff Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 15 Apr 2016 21:02:08 +0200 Subject: [PATCH 2/7] 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 5038e2a7051f242b21312ef8709c751dea3acd65 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 15 May 2016 22:46:50 -0500 Subject: [PATCH 3/7] 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 4/7] 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 5/7] 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 24024b90b64c45fc4e12c91353ad7980fadc57d6 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 16 May 2016 10:15:05 -0500 Subject: [PATCH 6/7] 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 7/7] 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);