Search code examples
postgresqlrustrust-diesel

Error extending Diesel with wrapper type for u128


I'm having difficulties with the following code where I am trying to create a custom type U128 for u128 which Diesel does not implement by default. My goal is to have a u128 primitive stored in a Postgresql database either as a text format or numeric (preferably numeric).

use std::io::Write;
use diesel::{AsExpression, deserialize, FromSqlRow, serialize};
use diesel::deserialize::{FromSql};
use diesel::pg::{Pg};
use diesel::serialize::{IsNull, Output, ToSql};
use diesel::sql_types::{Text};

#[derive(AsExpression, FromSqlRow, Debug, Clone, Copy)]
#[diesel(sql_type = Text)]
pub struct U128(pub u128);

impl ToSql<Text, Pg> for U128 {
    fn to_sql<'b>(&self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
        write!(out, "{}", self.0.to_string())?;
        Ok(IsNull::No)
    }
}

impl FromSql<Text, Pg> for U128 {
    fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
        let s = String::from_utf8_lossy(bytes.as_bytes());
        Ok(U128(s.parse()?))
    }
}

Here is the diesel struct I have created.

#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::balance)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Balance {
    pub id: i32,
    pub account_name: String,
    pub balance: u128,
}

This is the compile error I am getting when trying to use u128 in a diesel struct.

error[E0277]: the trait bound `u128: diesel::Queryable<diesel::sql_types::Text, Pg>` is not satisfied
   --> src\models.rs:117:27
    |
117 |     pub balance: u128,
    |                  ^^^^ the trait `diesel::Queryable<diesel::sql_types::Text, Pg>` is not implemented for `u128`
    |
    = help: the following other types implement trait `diesel::Queryable<ST, DB>`:
              i8
              i16
              i32
              i64
              u8
              u16
              u32
              u64
            and 2 others
    = note: required for `u128` to implement `FromSqlRow<diesel::sql_types::Text, Pg>`
    = help: see issue #48214

Solution

  • diesel doesn't magically apply any wrapper that might work, that's impossible, instead you have to tell it, which intermediate type it should use with (de)serialize_as attributes:

    #[derive(Queryable, Selectable)]
    #[diesel(table_name = crate::schema::balance)]
    #[diesel(check_for_backend(diesel::pg::Pg))]
    pub struct Balance {
        pub id: i32,
        pub account_name: String,
        #[diesel(serialize_as = U128, deserialize_as = U128)]
        pub balance: u128,
    }
    

    for that to work, diesel also needs a way to convert your wrapper from/to the target type, you can provide that with the From trait:

    impl From<u128> for U128 {
        fn from(v: u128) -> U128 {
            U128(v)
        }
    }
    
    impl From<U128> for u128 {
        fn from (v: U128) -> u128 {
            v.0
        }
    }
    

    Note: diesel uses TryInto for conversion, but since this one is infallible and there is a chain of blanket implementations that gets us there for free, I implemented From instead.