Search code examples
rustrust-diesel

Custom diesel type


I'm trying to implement a custom Diesel type using ToSql/FromSql traits.

My code now looks like this:

use diesel::{
    backend::Backend,
    deserialize,
    serialize,
    sql_types::VarChar,
};

#[derive(AsExpression, FromSqlRow, Debug)]
#[sql_type = "VarChar"]
pub struct File {
    id: String,
}

impl File {
    pub fn new(id: String) -> Self {
        Self { id }
    }
}


impl<B: Backend> serialize::ToSql<VarChar, B> for File {
    fn to_sql(&self, out: &mut serialize::Output<B>) -> serialize::Result {
        <String as serialize::ToSql<VarChar, B>>::to_sql(&self.id, out)
    }
}

impl<B: Backend<RawValue=[u8]>> deserialize::FromSql<VarChar, B> for File {
    fn from_sql(bytes: Option<&B::RawValue>) -> deserialize::Result<Self> {
        <String as deserialize::FromSql<VarChar, B>>::from_sql(bytes).map(|id| File::new(id))
    }
}

When I try to compile it, I receive a bunch of errors. A couple of them are related to inability to detect names.

error: cannot find derive macro `AsExpression` in this scope
 --> src\file.rs:8:10                                       
  |                                                         
8 | #[derive(AsExpression, FromSqlRow, Debug)]              
  |          ^^^^^^^^^^^^                                   
  |                                                         
  = note: consider importing this derive macro:             
          diesel::AsExpression                              
                                                            
error: cannot find derive macro `FromSqlRow` in this scope  
 --> src\file.rs:8:24                                       
  |                                                         
8 | #[derive(AsExpression, FromSqlRow, Debug)]              
  |                        ^^^^^^^^^^                       
  |                                                         
  = note: consider importing one of these items:            
          crate::file::deserialize::FromSqlRow              
          diesel::FromSqlRow                                

error: cannot find attribute `sql_type` in this scope
 --> src\file.rs:9:3
  |
9 | #[sql_type = "VarChar"]
  |   ^^^^^^^^

I thought the problem was because I hadn't added Diesel's prelude to my scope. But, unfortunately, when I do this, I encounter the same problem.

The second problem looks like this:

error[E0212]: cannot use the associated type of a trait with uninferred generic 
parameters
  --> src\file.rs:30:32
   |
30 |     fn from_sql(bytes: Option<&B::RawValue>) -> deserialize::Result<Self> {
   |                                ^^^^^^^^^^^
   |
help: use a fully qualified path with inferred lifetimes
   |
30 |     fn from_sql(bytes: Option<&<B as backend::private::HasRawValue<'_>>::Ra
wValue>) -> deserialize::Result<Self> {
   |                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  

The compiler requires a fully qualified syntax although each example of custom types shows almost the same code.


Solution

  • It is not clear to me what version of Diesel you are using; there were plenty of changes between v1.4.8 and v2.0.0 that are relevant here. It also seems like you are using aspects of each version, but for completeness' sake, I'll answer for both.

    Diesel 1.4.8

    Since this version was created back with Rust 2015 edition, it requires you to import macros like this at the root of your crate with #[macro_use] (as shown in the documentation for the table! macro for example).

    The method ToSql::to_sql uses an additional W: std::io::Write type parameter that you omitted (maybe from looking at the newest version).

    The complete example:

    #[macro_use]
    extern crate diesel;
    
    use diesel::{backend::Backend, deserialize, serialize, sql_types::VarChar};
    
    #[derive(AsExpression, FromSqlRow, Debug)]
    #[sql_type = "VarChar"]
    pub struct File {
        id: String,
    }
    
    impl File {
        pub fn new(id: String) -> Self {
            Self { id }
        }
    }
    
    impl<B: Backend> serialize::ToSql<VarChar, B> for File {
        fn to_sql<W: std::io::Write>(&self, out: &mut serialize::Output<W, B>) -> serialize::Result {
            <String as serialize::ToSql<VarChar, B>>::to_sql(&self.id, out)
        }
    }
    
    impl<B: Backend<RawValue = [u8]>> deserialize::FromSql<VarChar, B> for File {
        fn from_sql(bytes: Option<&B::RawValue>) -> deserialize::Result<Self> {
            <String as deserialize::FromSql<VarChar, B>>::from_sql(bytes).map(|id| File::new(id))
        }
    }
    

    Diesel 2.0.0

    This version, the crate was updated to Rust 2018 edition, meaning you no longer needed to use #[macro_use] but you still have to import them as normal items.

    The macro attributes all changes to be wrapped by diesel() to avoid inter-crate ambiguity. So #[sql_type = "VarChar"] was changed to #[diesel(sql_type = VarChar)].

    The method FromSql::from_sql changed its parameter from Option<&B::RawValue> to RawValue<B>, and the ToSql::to_sql method sprouted some lifetime annotations. Both also needed an additional constraint since I don't think its guaranteed that a B: Backend has a String: ToSql/FromSql implementation.

    The complete example:

    use diesel::{
        backend::{Backend, RawValue},
        deserialize, serialize,
        sql_types::VarChar,
        AsExpression, FromSqlRow,
    };
    
    #[derive(AsExpression, FromSqlRow, Debug)]
    #[diesel(sql_type = VarChar)]
    pub struct File {
        id: String,
    }
    
    impl File {
        pub fn new(id: String) -> Self {
            Self { id }
        }
    }
    
    impl<B: Backend> serialize::ToSql<VarChar, B> for File
    where
        String: serialize::ToSql<VarChar, B>,
    {
        fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, B>) -> serialize::Result {
            <String as serialize::ToSql<VarChar, B>>::to_sql(&self.id, out)
        }
    }
    
    impl<B: Backend> deserialize::FromSql<VarChar, B> for File
    where
        String: deserialize::FromSql<VarChar, B>,
    {
        fn from_sql(bytes: RawValue<B>) -> deserialize::Result<Self> {
            <String as deserialize::FromSql<VarChar, B>>::from_sql(bytes).map(|id| File::new(id))
        }
    }