From c42175a4244aee9777a0a411872a57c778328af1 Mon Sep 17 00:00:00 2001 From: Simon Bernier St-Pierre Date: Mon, 8 Apr 2019 14:19:42 -0400 Subject: [PATCH] add support for Uuid --- .travis.yml | 1 + Cargo.toml | 2 ++ README.md | 1 + src/row.rs | 2 ++ src/types/from_sql.rs | 30 +++++++++++++++++++++++++++--- src/types/to_sql.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/types/value.rs | 7 +++++++ 7 files changed, 78 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7624ef7..fe08cf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ script: - cargo test --features bundled - cargo test --features sqlcipher - cargo test --features i128_blob + - cargo test --features uuid - cargo test --features "unlock_notify bundled" - cargo test --features "array bundled csvtab vtab" - cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url vtab" diff --git a/Cargo.toml b/Cargo.toml index b9b1db6..d42ee5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,11 +62,13 @@ byteorder = { version = "1.2", features = ["i128"], optional = true } fallible-iterator = "0.2" fallible-streaming-iterator = "0.1" memchr = "2.2.0" +uuid = { version = "0.7", optional = true } [dev-dependencies] tempdir = "0.3" lazy_static = "1.0" regex = "1.0" +uuid = { version = "0.7", features = ["v4"] } [dependencies.libsqlite3-sys] path = "libsqlite3-sys" diff --git a/README.md b/README.md index 93d6f1d..6496e1b 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s * [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust. * [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. * `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected. +* `uuid` allows storing and retrieving `Uuid` values from the [`uuid`](https://docs.rs/uuid/) using blobs. * [`session`](https://sqlite.org/sessionintro.html), Session module extension. ## Notes on building rusqlite and libsqlite3-sys diff --git a/src/row.rs b/src/row.rs index 45d9739..b761ccd 100644 --- a/src/row.rs +++ b/src/row.rs @@ -230,6 +230,8 @@ impl<'stmt> Row<'stmt> { } #[cfg(feature = "i128_blob")] FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), + #[cfg(feature = "uuid")] + FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()), }) } diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index ce679aa..e621570 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -18,6 +18,11 @@ pub enum FromSqlError { #[cfg(feature = "i128_blob")] InvalidI128Size(usize), + /// Error returned when reading a `uuid` from a blob with a size + /// other than 16. Only available when the `uuid` feature is enabled. + #[cfg(feature = "uuid")] + InvalidUuidSize(usize), + /// An error case available for implementors of the `FromSql` trait. Other(Box), } @@ -29,6 +34,8 @@ impl PartialEq for FromSqlError { (FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2, #[cfg(feature = "i128_blob")] (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2, + #[cfg(feature = "uuid")] + (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2, (_, _) => false, } } @@ -43,6 +50,10 @@ impl fmt::Display for FromSqlError { FromSqlError::InvalidI128Size(s) => { write!(f, "Cannot read 128bit value out of {} byte blob", s) } + #[cfg(feature = "uuid")] + FromSqlError::InvalidUuidSize(s) => { + write!(f, "Cannot read UUID value out of {} byte blob", s) + } FromSqlError::Other(ref err) => err.fmt(f), } } @@ -55,6 +66,8 @@ impl Error for FromSqlError { FromSqlError::OutOfRange(_) => "value out of range", #[cfg(feature = "i128_blob")] FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value", + #[cfg(feature = "uuid")] + FromSqlError::InvalidUuidSize(_) => "unexpected blob size for UUID value", FromSqlError::Other(ref err) => err.description(), } } @@ -64,9 +77,7 @@ impl Error for FromSqlError { fn cause(&self) -> Option<&dyn Error> { match *self { FromSqlError::Other(ref err) => err.cause(), - FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None, - #[cfg(feature = "i128_blob")] - FromSqlError::InvalidI128Size(_) => None, + _ => None, } } } @@ -176,6 +187,19 @@ impl FromSql for i128 { } } +#[cfg(feature = "uuid")] +impl FromSql for uuid::Uuid { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + value + .as_blob() + .and_then(|bytes| { + uuid::Builder::from_slice(bytes) + .map_err(|_| FromSqlError::InvalidUuidSize(bytes.len())) + }) + .map(|mut builder| builder.build()) + } +} + impl FromSql for Option { fn column_result(value: ValueRef<'_>) -> FromSqlResult { match value { diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs index 06b6ce1..3a7481a 100644 --- a/src/types/to_sql.rs +++ b/src/types/to_sql.rs @@ -65,6 +65,9 @@ from_value!(Vec); #[cfg(feature = "i128_blob")] from_value!(i128); +#[cfg(feature = "uuid")] +from_value!(uuid::Uuid); + impl ToSql for ToSqlOutput<'_> { fn to_sql(&self) -> Result> { Ok(match *self { @@ -128,6 +131,9 @@ to_sql_self!(f64); #[cfg(feature = "i128_blob")] to_sql_self!(i128); +#[cfg(feature = "uuid")] +to_sql_self!(uuid::Uuid); + impl ToSql for &'_ T where T: ToSql, @@ -255,4 +261,36 @@ mod test { ] ); } + + #[cfg(feature = "uuid")] + #[test] + fn test_uuid() { + use crate::{params, Connection}; + use uuid::Uuid; + + let db = Connection::open_in_memory().unwrap(); + db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);") + .unwrap(); + + let id = Uuid::new_v4(); + + db.execute( + "INSERT INTO foo (id, label) VALUES (?, ?)", + params![id, "target"], + ) + .unwrap(); + + let mut stmt = db + .prepare("SELECT id, label FROM foo WHERE id = ?") + .unwrap(); + + let mut rows = stmt.query(params![id]).unwrap(); + let row = rows.next().unwrap().unwrap(); + + let found_id: Uuid = row.get_unwrap(0); + let found_label: String = row.get_unwrap(1); + + assert_eq!(found_id, id); + assert_eq!(found_label, "target"); + } } diff --git a/src/types/value.rs b/src/types/value.rs index 21b8759..8a2fffe 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -48,6 +48,13 @@ impl From for Value { } } +#[cfg(feature = "uuid")] +impl From for Value { + fn from(id: uuid::Uuid) -> Value { + Value::Blob(id.as_bytes().to_vec()) + } +} + macro_rules! from_i64( ($t:ty) => ( impl From<$t> for Value {