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

Difference between two implementations of user types #1550

Closed
vityafx opened this Issue Feb 9, 2018 · 7 comments

Comments

Projects
None yet
3 participants
@vityafx

vityafx commented Feb 9, 2018

I have tried implementing a mapping for my custom type by my own at first. Then I tried to do the same according to the example in this repository. However, both of ways seem to work. Could anyone outline differences for me?

First way:

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromSqlRow)]
pub enum PunishmentType {
    /// The user is locked from adding to queues.
    Locked,
    /// The user can't speak but can read the chat.
    Muted,
    /// The user is banned from the server.
    Banned,
}

impl From<i32> for PunishmentType {
    fn from(value: i32) -> PunishmentType {
        match value {
            0 => PunishmentType::Locked,
            1 => PunishmentType::Muted,
            _ => PunishmentType::Banned,
        }
    }
}

impl diesel::Expression for PunishmentType {
    type SqlType = diesel::sql_types::Integer;
}

impl<ST, DB> ToSql<ST, DB> for PunishmentType
where
    i32: ToSql<ST, DB>,
    DB: Backend,
{
    fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> serialize::Result {
        (*self as i32).to_sql(out)
    }
}

impl<ST, DB> FromSql<ST, DB> for PunishmentType
where
    i32: FromSql<ST, DB>,
    DB: Backend,
{
    fn from_sql(value: Option<&<DB as Backend>::RawValue>) -> deserialize::Result<Self> {
        <i32 as FromSql<ST, DB>>::from_sql(value).map(PunishmentType::from)
    }
}

Second way:

#[derive(FromSqlRow, AsExpression)]
#[diesel(foreign_derive)]
#[sql_type = "Integer"]
struct PunishmentTypeProxy(PunishmentType);

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PunishmentType {
    /// The user is locked from adding to queues.
    Locked,
    /// The user can't speak but can read the chat.
    Muted,
    /// The user is banned from the server.
    Banned,
}

impl From<i32> for PunishmentType {
    fn from(value: i32) -> PunishmentType {
        match value {
            0 => PunishmentType::Locked,
            1 => PunishmentType::Muted,
            _ => PunishmentType::Banned,
        }
    }
}

impl<ST, DB> FromSql<ST, DB> for PunishmentType
where
    i32: FromSql<ST, DB>,
    DB: Backend,
{
    fn from_sql(value: Option<&<DB as Backend>::RawValue>) -> deserialize::Result<Self> {
        <i32 as FromSql<ST, DB>>::from_sql(value).map(PunishmentType::from)
    }
}

I mean, if there is no difference, what is the Proxy-structure is used for? Thanks.

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 9, 2018

Your first impl won't work. It'll fail when you try to execute a query containing a PunishmentTypebecause you did not implement QueryId or QueryFragment. The second version relies on API which is not public. foreign_derive is for use in Diesel only. You should put #[derive(AsExpression)] on PunishmentType directly.

@sgrif sgrif closed this Feb 9, 2018

@vityafx

This comment has been minimized.

vityafx commented Feb 12, 2018

So I end up with this:

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromSqlRow, AsExpression)]
#[sql_type = "Integer"]
pub enum PunishmentType {
    /// The user is locked from adding to game queues.
    Locked,
    /// The user can't speak but can read the chat.
    Muted,
    /// The user is banned from the server.
    Banned,
}

impl From<i32> for PunishmentType {
    fn from(value: i32) -> PunishmentType {
        match value {
            0 => PunishmentType::Locked,
            1 => PunishmentType::Muted,
            _ => PunishmentType::Banned,
        }
    }
}

impl<ST, DB> FromSql<ST, DB> for PunishmentType
where
    i32: FromSql<ST, DB>,
    DB: Backend,
{
    fn from_sql(value: Option<&<DB as Backend>::RawValue>) -> deserialize::Result<Self> {
        <i32 as FromSql<ST, DB>>::from_sql(value).map(PunishmentType::from)
    }
}

But as you said, when I try to execute a query I get problems:

error[E0277]: the trait bound `diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>: diesel::insertable::InsertValues<db::schema::punishments::table, diesel::sqlite::Sqlite>` is not satisfied
  --> src/db/punishments.rs:56:10
   |
56 |         .execute(&*connection)?)
   |          ^^^^^^^ the trait `diesel::insertable::InsertValues<db::schema::punishments::table, diesel::sqlite::Sqlite>` is not implemented for `diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>`
   |
   = help: the following implementations were found:
             <diesel::insertable::ColumnInsertValue<Col, Expr> as diesel::insertable::InsertValues<<Col as diesel::Column>::Table, DB>>
             <diesel::insertable::ColumnInsertValue<Col, Expr> as diesel::insertable::InsertValues<<Col as diesel::Column>::Table, diesel::sqlite::Sqlite>>
   = note: required because of the requirements on the impl of `diesel::insertable::InsertValues<db::schema::punishments::table, diesel::sqlite::Sqlite>` for `(diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, &i32>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::user_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::server_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::started_at, diesel::expression::bound::Bound<diesel::sql_types::Timestamp, &chrono::NaiveDateTime>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::duration, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &chrono::Duration>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::reason, diesel::expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::processed, diesel::expression::bound::Bound<diesel::sql_types::Bool, &bool>>)`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<diesel::sqlite::Sqlite>` for `diesel::query_builder::ValuesClause<(diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, &i32>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::user_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::server_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::started_at, diesel::expression::bound::Bound<diesel::sql_types::Timestamp, &chrono::NaiveDateTime>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::duration, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &chrono::Duration>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::reason, diesel::expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::processed, diesel::expression::bound::Bound<diesel::sql_types::Bool, &bool>>), db::schema::punishments::table>`
   = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<diesel::sqlite::Sqlite>` for `diesel::query_builder::InsertStatement<db::schema::punishments::table, diesel::query_builder::ValuesClause<(diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, &i32>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::user_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::server_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::started_at, diesel::expression::bound::Bound<diesel::sql_types::Timestamp, &chrono::NaiveDateTime>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::duration, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &chrono::Duration>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::reason, diesel::expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::processed, diesel::expression::bound::Bound<diesel::sql_types::Bool, &bool>>), db::schema::punishments::table>, diesel::query_builder::insert_statement::Replace>`
   = note: required because of the requirements on the impl of `diesel::query_dsl::load_dsl::ExecuteDsl<_, diesel::sqlite::Sqlite>` for `diesel::query_builder::InsertStatement<db::schema::punishments::table, diesel::query_builder::ValuesClause<(diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::id, diesel::expression::bound::Bound<diesel::sql_types::Integer, &i32>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::user_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::server_id, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &i64>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::started_at, diesel::expression::bound::Bound<diesel::sql_types::Timestamp, &chrono::NaiveDateTime>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::duration, diesel::expression::bound::Bound<diesel::sql_types::BigInt, &chrono::Duration>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::reason, diesel::expression::bound::Bound<diesel::sql_types::Text, &std::string::String>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::punishment_type, diesel::expression::bound::Bound<diesel::sql_types::Integer, &db::models::PunishmentType>>, diesel::insertable::ColumnInsertValue<db::schema::punishments::columns::processed, diesel::expression::bound::Bound<diesel::sql_types::Bool, &bool>>), db::schema::punishments::table>, diesel::query_builder::insert_statement::Replace>`

error: aborting due to previous error

How do I fix this?

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 12, 2018

I'd need to see more of your schema to answer that, but I'm recovering from a concussion and can't help you right now. Try asking in gitter

@sgrif

This comment has been minimized.

Member

sgrif commented Feb 12, 2018

Oh also it looks like the problem is that you don't have a ToSql implementation

@vityafx

This comment has been minimized.

vityafx commented Feb 12, 2018

When I had it there was another error:

error[E0119]: conflicting implementations of trait `diesel::serialize::ToSql<diesel::sql_types::Nullable<diesel::sql_types::Integer>, _>` for type `db::models::PunishmentType`:
  --> src/db/models.rs:16:74
   |
16 |   #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromSqlRow, AsExpression)]
   |                                                                            ^^^^^^^^^^^^ conflicting implementation for `db::models::PunishmentType`
...
41 | / impl<ST, DB> ToSql<ST, DB> for PunishmentType
42 | | where
43 | |     i32: ToSql<ST, DB>,
44 | |     DB: Backend,
...  |
48 | |     }
49 | | }
   | |_- first implementation here

My table is:

#[derive(Debug, Clone, Queryable, Insertable)]
#[table_name = "punishments"]
pub struct Punishment {
    /// The ID of the punishment in the table.
    pub id: i32,
    /// The punished user id
    pub user_id: i64,
    /// The server id on which the user is punished on
    pub server_id: i64,
    /// The punishment start time
    pub started_at: NaiveDateTime,
    /// The punishment duration
    pub duration: chrono::Duration,
    /// The punishment reason
    pub reason: String,
    /// The punishment type
    pub punishment_type: PunishmentType,
    /// The processed mark. The punishment is processed when it is expired and all the things were done. This mark
    /// can also be used for checking whether it is needed to do something if the punishment is expired or not.
    pub processed: bool,
}

and schema:

table! {
    punishments (id) {
        id -> Integer,
        user_id -> BigInt,
        server_id -> BigInt,
        started_at -> Timestamp,
        duration -> BigInt,
        reason -> Text,
        punishment_type -> Integer,
        processed -> Bool,
    }
}

Thank you for trying to help me :)

@weiznich

This comment has been minimized.

Contributor

weiznich commented Feb 12, 2018

The following code works for me:

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromSqlRow, AsExpression)]
#[sql_type = "Integer"]
pub enum PunishmentType {
    /// The user is locked from adding to game queues.
    Locked = 1,
    /// The user can't speak but can read the chat.
    Muted = 2,
    /// The user is banned from the server.
    Banned = 3,
}

impl From<i32> for PunishmentType {
    fn from(value: i32) -> PunishmentType {
        match value {
            0 => PunishmentType::Locked,
            1 => PunishmentType::Muted,
            _ => PunishmentType::Banned,
        }
    }
}

impl<DB> FromSql<Integer, DB> for PunishmentType
where
    i32: FromSql<Integer, DB>,
    DB: Backend,
{
    fn from_sql(value: Option<&<DB as Backend>::RawValue>) -> deserialize::Result<Self> {
        <i32 as FromSql<Integer, DB>>::from_sql(value).map(PunishmentType::from)
    }
}

impl<DB> ToSql<Integer, DB> for PunishmentType
where
    DB: Backend
{
    fn to_sql<W: Write>(&self, out: &mut serialize::Output<W, DB>) -> serialize::Result {
        <i32 as ToSql<Integer, DB>>::to_sql(&(*self as i32), out)
    }
}
@vityafx

This comment has been minimized.

vityafx commented Feb 12, 2018

@weiznich That did! How did it work? All you changed was a generic type to specific one...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment