add support for Uuid

This commit is contained in:
Simon Bernier St-Pierre 2019-04-08 14:19:42 -04:00
parent 22614c64bd
commit c42175a424
7 changed files with 78 additions and 3 deletions

View File

@ -40,6 +40,7 @@ script:
- cargo test --features bundled - cargo test --features bundled
- cargo test --features sqlcipher - cargo test --features sqlcipher
- cargo test --features i128_blob - cargo test --features i128_blob
- cargo test --features uuid
- cargo test --features "unlock_notify bundled" - cargo test --features "unlock_notify bundled"
- cargo test --features "array bundled csvtab vtab" - cargo test --features "array bundled csvtab vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url vtab" - cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace url vtab"

View File

@ -62,11 +62,13 @@ byteorder = { version = "1.2", features = ["i128"], optional = true }
fallible-iterator = "0.2" fallible-iterator = "0.2"
fallible-streaming-iterator = "0.1" fallible-streaming-iterator = "0.1"
memchr = "2.2.0" memchr = "2.2.0"
uuid = { version = "0.7", optional = true }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"
lazy_static = "1.0" lazy_static = "1.0"
regex = "1.0" regex = "1.0"
uuid = { version = "0.7", features = ["v4"] }
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"

View File

@ -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. * [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function. * [`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. * `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. * [`session`](https://sqlite.org/sessionintro.html), Session module extension.
## Notes on building rusqlite and libsqlite3-sys ## Notes on building rusqlite and libsqlite3-sys

View File

@ -230,6 +230,8 @@ impl<'stmt> Row<'stmt> {
} }
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()),
#[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()),
}) })
} }

View File

@ -18,6 +18,11 @@ pub enum FromSqlError {
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
InvalidI128Size(usize), 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. /// An error case available for implementors of the `FromSql` trait.
Other(Box<dyn Error + Send + Sync>), Other(Box<dyn Error + Send + Sync>),
} }
@ -29,6 +34,8 @@ impl PartialEq for FromSqlError {
(FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2, (FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
(FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2, (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
#[cfg(feature = "uuid")]
(FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
(_, _) => false, (_, _) => false,
} }
} }
@ -43,6 +50,10 @@ impl fmt::Display for FromSqlError {
FromSqlError::InvalidI128Size(s) => { FromSqlError::InvalidI128Size(s) => {
write!(f, "Cannot read 128bit value out of {} byte blob", 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), FromSqlError::Other(ref err) => err.fmt(f),
} }
} }
@ -55,6 +66,8 @@ impl Error for FromSqlError {
FromSqlError::OutOfRange(_) => "value out of range", FromSqlError::OutOfRange(_) => "value out of range",
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => "unexpected blob size for 128bit value", 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(), FromSqlError::Other(ref err) => err.description(),
} }
} }
@ -64,9 +77,7 @@ impl Error for FromSqlError {
fn cause(&self) -> Option<&dyn Error> { fn cause(&self) -> Option<&dyn Error> {
match *self { match *self {
FromSqlError::Other(ref err) => err.cause(), FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None, _ => None,
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => None,
} }
} }
} }
@ -176,6 +187,19 @@ impl FromSql for i128 {
} }
} }
#[cfg(feature = "uuid")]
impl FromSql for uuid::Uuid {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_blob()
.and_then(|bytes| {
uuid::Builder::from_slice(bytes)
.map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
})
.map(|mut builder| builder.build())
}
}
impl<T: FromSql> FromSql for Option<T> { impl<T: FromSql> FromSql for Option<T> {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {

View File

@ -65,6 +65,9 @@ from_value!(Vec<u8>);
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
from_value!(i128); from_value!(i128);
#[cfg(feature = "uuid")]
from_value!(uuid::Uuid);
impl ToSql for ToSqlOutput<'_> { impl ToSql for ToSqlOutput<'_> {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(match *self { Ok(match *self {
@ -128,6 +131,9 @@ to_sql_self!(f64);
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
to_sql_self!(i128); to_sql_self!(i128);
#[cfg(feature = "uuid")]
to_sql_self!(uuid::Uuid);
impl<T: ?Sized> ToSql for &'_ T impl<T: ?Sized> ToSql for &'_ T
where where
T: ToSql, 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");
}
} }

View File

@ -48,6 +48,13 @@ impl From<i128> for Value {
} }
} }
#[cfg(feature = "uuid")]
impl From<uuid::Uuid> for Value {
fn from(id: uuid::Uuid) -> Value {
Value::Blob(id.as_bytes().to_vec())
}
}
macro_rules! from_i64( macro_rules! from_i64(
($t:ty) => ( ($t:ty) => (
impl From<$t> for Value { impl From<$t> for Value {