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); + } +}