Search code examples
rustrust-diesel

Error Creating Custom Type Wrappers in Diesel


I am trying to create custom types around H160 and U256 so that I can use them with Diesel.

Here is the code I used to create the custom types:

use diesel::deserialize::{self, FromSql};
use diesel::pg::Pg;
use diesel::serialize::{self, Output, ToSql};
use diesel::sql_types::*;
use diesel::{backend::Backend, expression::AsExpression};
use ethers::{
    prelude::{Address as EthereumAddress, U256 as Eth256, *},
};
use serde::Serialize;
use std::io;
use std::io::Write;

table! {
    ethbalances (id) {
        id -> Int4,
        account -> Varchar,
        balance -> Int4,
        last_updated -> Text,
        holder -> Bool,
    }
}

#[derive(AsExpression, FromSqlRow, Debug, Copy, Clone, Serialize)]
#[sql_type = "Varchar"]
pub struct Address {
    value: EthereumAddress,
}
// Something to do with incompatible type
// https://stackoverflow.com/questions/62746540/diesel-with-custom-wrapper-types
// https://stackoverflow.com/questions/49092437/how-do-i-implement-queryable-and-insertable-for-custom-field-types-in-diesel
// https://stackoverflow.com/questions/65158596/rust-diesel-orm-queries
// https://stackoverflow.com/questions/47874398/how-do-i-combine-multiple-functions-using-diesel-into-one-through-abstraction

impl ToSql<VarChar, Pg> for Address {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
        <VarChar as ToSql<VarChar, Pg>>::to_sql(&self.value, out)
    }
}

impl<DB: Backend<RawValue = [u8]>> FromSql<Varchar, DB> for Address {
    fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
        <VarChar as FromSql<VarChar, Pg>>::from_sql(bytes).map(|value| Address { value })
    }
}

#[derive(AsExpression, FromSqlRow, Debug, Copy, Clone, Serialize)]
#[sql_type = "Integer"]
pub struct U256 {
    value: Eth256,
}
// Something to do with incompatible type
impl ToSql<Integer, Pg> for U256 {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
        <Integer as ToSql<Integer, Pg>>::to_sql(&self.value, out)
    }
}

impl ToSql<diesel::sql_types::Uuid, Pg> for PostId {
    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
        <uuid::Uuid as ToSql<diesel::sql_types::Uuid, Pg>>::to_sql(&self.value, out)
    }
}

impl<DB: Backend<RawValue = [u8]>> FromSql<Integer, DB> for U256 {
    fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
        <Integer as FromSql<Integer, Pg>>::from_sql(bytes).map(|value| U256 { value })
    }
}

#[derive(Queryable, Insertable, Serialize)]
#[table_name = "ethbalances"]
pub struct ETHRioBalance {
    id: i32,
    account: Address,
    balance: U256,
    holder: bool,
}

Here is my up.sql file that generates the table! macro

CREATE TABLE ethbalances (
    id SERIAL PRIMARY KEY,
    account  NOT NULL,
    balance INTEGER NOT NULL,
    last_updated TEXT NOT NULL,
    holder BOOLEAN NOT NUll
  )

Unfortunately , I get the following error

error[E0277]: the trait bound `diesel::sql_types::Text: ToSql<diesel::sql_types::Text, Pg>` is not satisfied
  --> src/schema.rs:48:9
   |
48 |         <VarChar as ToSql<VarChar, Pg>>::to_sql(&self.value, out)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ToSql<diesel::sql_types::Text, Pg>` is not implemented for `diesel::sql_types::Text`

error[E0308]: mismatched types
  --> src/schema.rs:48:49
   |
48 |         <VarChar as ToSql<VarChar, Pg>>::to_sql(&self.value, out)
   |                                                 ^^^^^^^^^^^ expected struct `diesel::sql_types::Text`, found struct `H160`
   |
   = note: expected reference `&diesel::sql_types::Text`
              found reference `&H160`

error[E0277]: the trait bound `diesel::sql_types::Text: FromSql<diesel::sql_types::Text, Pg>` is not satisfied
  --> src/schema.rs:54:29
   |
54 |         <VarChar as FromSql<VarChar, Pg>>::from_sql(bytes).map(|value| Address { value })
   |                             ^^^^^^^ the trait `FromSql<diesel::sql_types::Text, Pg>` is not implemented for `diesel::sql_types::Text`

error[E0308]: mismatched types
  --> src/schema.rs:54:82
   |
54 |         <VarChar as FromSql<VarChar, Pg>>::from_sql(bytes).map(|value| Address { value })
   |                                                                                  ^^^^^ expected struct `H160`, found struct `diesel::sql_types::Text`

error[E0277]: the trait bound `diesel::sql_types::Integer: ToSql<diesel::sql_types::Integer, Pg>` is not satisfied
  --> src/schema.rs:66:9
   |
66 |         <Integer as ToSql<Integer, Pg>>::to_sql(&self.value, out)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ToSql<diesel::sql_types::Integer, Pg>` is not implemented for `diesel::sql_types::Integer`

error[E0308]: mismatched types
  --> src/schema.rs:66:49
   |
66 |         <Integer as ToSql<Integer, Pg>>::to_sql(&self.value, out)
   |                                                 ^^^^^^^^^^^ expected struct `diesel::sql_types::Integer`, found struct `ethers::prelude::U256`
   |
   = note: expected reference `&diesel::sql_types:x:Integer`
              found reference `&ethers::prelude::U256`

error[E0277]: the trait bound `diesel::sql_types::Integer: FromSql<diesel::sql_types::Integer, Pg>` is not satisfied
  --> src/schema.rs:78:29
   |
78 |         <Integer as FromSql<Integer, Pg>>::from_sql(bytes).map(|value| U256 { value })
   |                             ^^^^^^^ the trait `FromSql<diesel::sql_types::Integer, Pg>` is not implemented for `diesel::sql_types::Integer`

error[E0308]: mismatched types
  --> src/schema.rs:78:79
   |
78 |         <Integer as FromSql<Integer, Pg>>::from_sql(bytes).map(|value| U256 { value })
   |                                                                               ^^^^^ expected struct `ethers::prelude::U256`, found struct `diesel::sql_types::Integer`

I will appreciate any guidance on what I am doing wrong.


Solution

  • The easiest way to implement ToSql and FromSql for a custom type is to simply convert it into a diesel-known type and defer to its implementation. Here are working samples for ethers::Address using String as the intermediate type.

    Using Diesel v1.4.8: heed the documentation for ToSql and FromSql for backend-specific details.

    #[macro_use]
    extern crate diesel;
    
    use diesel::deserialize::{self, FromSql};
    use diesel::pg::Pg;
    use diesel::serialize::{self, Output, ToSql};
    use diesel::sql_types::*;
    
    use ethers::prelude::Address as EthereumAddress;
    
    #[derive(AsExpression, FromSqlRow, Debug, Copy, Clone)]
    #[sql_type = "VarChar"]
    pub struct Address {
        value: EthereumAddress,
    }
    
    impl ToSql<VarChar, Pg> for Address {
        fn to_sql<W: std::io::Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
            <String as ToSql<VarChar, Pg>>::to_sql(&self.value.to_string(), out)
        }
    }
    
    impl FromSql<Varchar, Pg> for Address {
        fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
            <String as FromSql<VarChar, Pg>>::from_sql(bytes).map(|s| Address { value: s.parse().unwrap() })
        }
    }
    

    Using Diesel v2.0.0: heed the documentation for ToSql and FromSql for backend-specific details.

    use diesel::backend::RawValue;
    use diesel::deserialize::{self, FromSql, FromSqlRow};
    use diesel::expression::AsExpression;
    use diesel::pg::Pg;
    use diesel::serialize::{self, Output, ToSql};
    use diesel::sql_types::*;
    
    use ethers::prelude::Address as EthereumAddress;
    
    #[derive(AsExpression, FromSqlRow, Debug, Copy, Clone)]
    #[diesel(sql_type = VarChar)]
    pub struct Address {
        value: EthereumAddress,
    }
    
    impl ToSql<VarChar, Pg> for Address {
        fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
            <String as ToSql<VarChar, Pg>>::to_sql(&self.value.to_string(), &mut out.reborrow())
        }
    }
    
    impl FromSql<Varchar, Pg> for Address {
        fn from_sql(bytes: RawValue<Pg>) -> deserialize::Result<Self> {
            <String as FromSql<VarChar, Pg>>::from_sql(bytes).map(|s| Address { value: s.parse().unwrap() })
        }
    }
    

    You can checkout the Diesel 2.0 migration guide for more details on changes and upgrading.