0

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.

0xsegfault
  • 2,899
  • 6
  • 28
  • 58
  • `VarChar as ToSql` is meaningless, the `diesel::sql_types` are simply markers to indicate what type they map to and from. You want something more like `String as ToSql` and do the conversions between `EthereumAddress` and `String` manually. Not sure what you want to do for the `Eth256` case since an SQL `INTEGER` is way smaller than 256-bit. – kmdreko May 17 '22 at 16:03
  • Thanks we can ignore the Varchar / String as rustc interprets it as an alias. However , this does not address my isssue. Thanks re: The integer error , it was probably going to bubble up next – 0xsegfault May 17 '22 at 17:10
  • I think you misunderstood what I was saying. And `VarChar` is not an alias for `String`. – kmdreko May 18 '22 at 03:30

1 Answers1

2

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.

kmdreko
  • 42,554
  • 6
  • 57
  • 106