New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Uuid to SQLite #364
Comments
As far as I'm aware there is no data type in SQLite that maps to UUID? |
I'm sorry. You're totally right! |
No there is not, but the UUID could just be (de)serialized to |
I've implemented storing UUIDs in SQLite using I can make a pull request with an adaptation of this code if anyone is interested. |
I'm interested @0xcaff, would appreciate it if you have the time to do a PR for this. |
I’d like thoughts from a contributor before making a PR. Thoughts @sgrif? |
Our policy for including mappings between certain SQL types an rust types is like following:
Given that (and given that it is quite easy to write a new type wrapper for this outside of diesel) I would say that impl should not be part of diesel itself. |
Playing devils advocate here. I'd argue that uuid is a fundamental type. Many applications use the Also, diesel/diesel/src/sql_types/mod.rs Lines 267 to 282 in 85e0007
|
Either it should be implemented for both or none. It's misleading to implement it for If it shouldn't be, is there some way this could be provided in a standardized manner? It is odd for users to have to manually implement |
In my opinion |
We also support it because SQLite does have a ton of built-in functions for dealing with dates. Yes, it represents them as strings, but that's ultimately an implementation detail, not something relevant to whether the type exists or not. Importantly, there is a canonical way to represent a date in SQLite. That is not the true of UUIDs. We could store the raw bytes, or we could store the text representation. It's not obvious which we should choose, and we would be incompatible with existing databases when we choose one or the other. Ultimately when there's a clear representation of a type and semantically it exists for a given backend, we support it even if there's some mismatch. For example, even PG's datetime type supports a different range of dates than chrono. Given that SQLite is fully dynamically typed, if we really wanted to only support what strictly could be represented, the only type that we could support for SQLite is |
Here is an updated version of the code shared by @0xcaff , as I couldn't get the original to work anymore. The "uuid" feature maybe should be called "postgres-uuid", took me a while to figure out it isn't implemented for sqlite. The same goes for most of the other features, like use uuid;
use std::io::prelude::*;
use diesel::deserialize::{self, FromSql};
use diesel::serialize::{self, IsNull, Output, ToSql};
use diesel::sql_types::{Binary};
use diesel::sqlite::Sqlite;
use diesel::backend::Backend;
use std::fmt::{Display, Formatter};
use std::fmt;
#[derive(Debug, Clone, Copy, FromSqlRow, AsExpression, Hash, Eq, PartialEq)]
#[sql_type = "Binary"]
pub struct UUID(pub uuid::Uuid);
impl UUID {
pub fn random() -> Self {
Self(uuid::Uuid::new_v4())
}
}
impl From<UUID> for uuid::Uuid {
fn from(s: UUID) -> Self {
s.0
}
}
impl Display for UUID {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromSql<Binary, Sqlite> for UUID {
fn from_sql(bytes: Option<&<Sqlite as Backend>::RawValue>) -> deserialize::Result<Self> {
let bytes = not_none!(bytes);
uuid::Uuid::from_slice(bytes.read_blob()).map(UUID).map_err(|e| e.into())
}
}
impl ToSql<Binary, Sqlite> for UUID {
fn to_sql<W: Write>(&self, out: &mut Output<W, Sqlite>) -> serialize::Result {
out.write_all(self.0.as_bytes())
.map(|_| IsNull::No)
.map_err(Into::into)
}
} |
+1 for rename of the feature. I've spent about an hour looking into this only to find out the type is only compat with PG. Using rusqlite allows for UUID usage as BLOB format, it would be really nice to see, but if it violates your standards then :( |
This was a little bit more complicated than expected. I had to get rid of the uuid, because sqlite doesn't support it and diesel refuses to include a converter for that. :-( diesel-rs/diesel#364 adding my own converter will come later. until then I just convert from uuid -> string -> uuid by myself.
Following on from @MightyPork's code, you can make the backend generic for all database types supporting the impl<B: diesel::backend::Backend> diesel::deserialize::FromSql<Binary, B> for Uuid
where
Vec<u8>: diesel::deserialize::FromSql<Binary, B>,
{
fn from_sql(bytes: Option<&B::RawValue>) -> diesel::deserialize::Result<Self> {
let value = <Vec<u8>>::from_sql(bytes)?;
uuid::Uuid::from_slice(&value)
.map(Uuid)
.map_err(|e| e.into())
}
}
impl<B: diesel::backend::Backend> diesel::serialize::ToSql<Binary, B> for Uuid
where
[u8]: diesel::serialize::ToSql<Binary, B>,
{
fn to_sql<W: Write>(
&self,
out: &mut diesel::serialize::Output<W, B>,
) -> diesel::serialize::Result {
out.write_all(self.0.as_bytes())
.map(|_| diesel::serialize::IsNull::No)
.map_err(Into::into)
}
} |
Based on w4's and MightyPork's solutions (which seem to require diesel v1.x), I adapted their UUID module for diesel v2.1.5: use diesel::{
backend::Backend,
deserialize::{self, FromSql},
serialize::{self, Output, ToSql},
sql_types::Binary,
AsExpression, FromSqlRow,
};
use std::fmt::{self, Display, Formatter};
use uuid;
#[derive(Debug, Default, Clone, Copy, FromSqlRow, AsExpression, Hash, Eq, PartialEq)]
#[diesel(sql_type = Binary)]
pub struct UUID(pub uuid::Uuid);
impl UUID {
pub fn new_v4() -> Self {
Self(uuid::Uuid::new_v4())
}
}
impl From<UUID> for uuid::Uuid {
fn from(s: UUID) -> Self {
s.0
}
}
impl From<uuid::Uuid> for UUID {
fn from(s: uuid::Uuid) -> Self {
UUID(s)
}
}
impl Display for UUID {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<B: Backend> FromSql<Binary, B> for UUID
where
Vec<u8>: FromSql<Binary, B>,
{
fn from_sql(bytes: <B as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <Vec<u8>>::from_sql(bytes)?;
uuid::Uuid::from_slice(&value)
.map(UUID)
.map_err(|e| e.into())
}
}
impl<B: Backend> ToSql<Binary, B> for UUID
where
[u8]: ToSql<Binary, B>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, B>) -> serialize::Result {
self.0.as_bytes().to_sql(out)
}
} This has worked for me in a small SQLite-based project. |
Could it be possible to save it in Text instead of Binary? |
Saving it as text is the default behavior which you get when you declare the field as create table posts
(
id uuid_text not null primary key,
text text not null,
); Then you just need to remember to always convert to and from String when writing and reading from the DB. If you want to map it transparently, I suppose you could do it analog to the implementation above: Replace // ...
#[diesel(sql_type = Text)]
pub struct UUID(pub uuid::Uuid);
// ...
impl<B: Backend> FromSql<Text, B> for UUID
where
String: FromSql<Text, B>,
{
fn from_sql(bytes: <B as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = String::from_sql(bytes)?;
uuid!(value).map(UUID).map_err(|e| e.into())
}
}
impl<B: Backend> ToSql<Binary, B> for UUID
where
str: ToSql<Text, B>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, B>) -> serialize::Result {
self.0.to_string().to_sql(out)
}
} I haven't tested this so you might have to put some more work into the methods, but that would be the rough idea. |
It would'nt let me do this because of str needing lifetime annotations or something like that (sorry I'm pretty new). Eventually I saw this in the documentation and ended up with this implementation that works fine: // ...
impl<B: Backend> FromSql<Text, B> for UUID
where
String: FromSql<Text, B>,
{
fn from_sql(bytes: <B as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = String::from_sql(bytes)?;
uuid::Uuid::from_str(&value.as_str())
.map(UUID)
.map_err(|e| e.into())
}
}
impl ToSql<Text, Sqlite> for UUID
where
String: ToSql<Text, Sqlite>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(self.0.as_hyphenated().to_string());
Ok(IsNull::No)
}
} Thank you very much for your help @teschmitt |
It would be nice to be able to use
uuid::Uuid
within the structs in combination of SQLite!The text was updated successfully, but these errors were encountered: