Enum in Diesel ORM with implemented FromSql and ToSql

Hello.
I'am trying to use Enums with Diesel(postgres), but I'am stuck with serializing(Diesel ORM -> Postgres Types and via verse). Found this example in master and 2.2.x branches, but it didn't help me. Could you please help with next errors in code and answer on question how correct use(for what situation I need impl this examples and by what logic (except for the DBMS type) should I choose one or another interface for implementation in (ToSql in diesel::serialize - Rust) and FromSql Implementations on Foreign Types section?

I have:

[dependencies]
serde_json = "1.0.128"
diesel = { version = "2.2.0", features = ["postgres"] }

models.rs

use diesel::{Queryable, Insertable, Selectable};
use diesel::deserialize::{FromSqlRow, QueryableByName, FromSql};
use diesel::pg::{Pg, PgValue};
use diesel::serialize::{IsNull, Output, ToSql};
use diesel::sql_types::{Integer, SqlType};
use diesel::{serialize, deserialize};
use crate::schema;


#[derive(Debug, PartialEq, FromSqlRow, Eq)]
#[diesel(sql_type = schema::sql_types::RoleType)]
pub enum Role {
    Mr,
    Batrak,
    R,
}


#[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, Eq)]
#[diesel(sql_type = schema::sql_types::SexType)]
pub enum Sex {
    M = 0,
    F = 1,
}

impl ToSql<schema::sql_types::RoleType, Pg> for Role {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
        match *self {
            Role::Mr => out.write_all(b"mr")?,
            Role::Batrak => out.write_all(b"batrak")?,
            Role::R => out.write_all(b"r")?,
        }
        Ok(IsNull::No)
    }
}

impl ToSql<Integer, Pg> for Sex {
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
        match self {
            Sex::M => 0.to_sql(out)?,
            Sex::F => 1.to_sql(out)?,
        }
        Ok(IsNull::No)
    }
}


impl FromSql<schema::sql_types::RoleType, Pg> for Role {
    fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
        match bytes.as_bytes() {
            b"mr" => Ok(Role::Mr),
            b"batrak" => Ok(Role::Batrak),
            b"r" => Ok(Role::R),
            _ => Err("Unrecognized role variant".into()),
        }
    }
}


impl FromSql<Integer, Pg> for Sex {
    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
        match i32::from_sql(value)? {
            0 => Ok(Sex::M),
            1 => Ok(Sex::F),
            x => Err(format!("Unrecognized variant {}", x).into()),
        }
    }
}


#[derive(QueryableByName, Debug)]
#[diesel(table_name = gql_entity)]
pub struct SelectEntity {
    pub id: i32,
    pub name: String,
    pub project: String,
    pub role: Role,
    pub sex: Sex,
}


#[derive(Insertable)]
#[diesel(table_name = gql_entity)]
pub struct NewEntity<'a> {
    pub name: &'a str,
    pub project: &'a str,
    pub role: &'a Role,
    pub sex: &'a Sex
}

schema.rs
// @generated automatically by Diesel CLI.

pub mod sql_types {
    #[derive(
        diesel::query_builder::QueryId,
        Clone,
        diesel::sql_types::SqlType
    )]
    #[diesel(postgres_type(name = "role_type"))]
    pub struct RoleType;

    #[derive(
        diesel::query_builder::QueryId,
        Clone,
        diesel::sql_types::SqlType
    )]
    #[diesel(postgres_type(name = "sex_type"))]
    pub struct SexType;
}

diesel::table! {
    use diesel::sql_types::*;
    use super::sql_types::RoleType;
    use super::sql_types::SexType;

    gql_entity (id) {
        id -> Int4,
        name -> Varchar,
        project -> Varchar,
        role -> RoleType,
        sex -> SexType,
    }
}

up migration
-- Your SQL goes here

CREATE TYPE role_type AS ENUM ('mr', 'batrak', 'r');
CREATE TYPE sex_type AS ENUM (0, 1);

CREATE TABLE gql_entity (
    id SERIAL PRIMARY KEY,
    name VARCHAR NOT NULL UNIQUE,
    project VARCHAR NOT NULL,
    role role_type NOT NULL,
    sex sex_type NOT NULL
)

and few methods that raises errors

pub fn create_gql_entity(&mut self, new_entity: NewEntity) {
        diesel::insert_into(schema::gql_entity::table)
            .values(&new_entity)
            .execute(&mut self.connection)
            .expect("Fail to add new entity");
    }

pub fn get_all(&mut self) -> Vec<SelectEntity> {
        schema::gql_entity::table
            .load::<SelectEntity>(&mut self.connection)
            .expect("Fail to get all entities")
    }

in

schema::gql_entity::table
            .load::<SelectEntity>(&mut self.connection)

have error:

error[E0277]: the trait bound (Integer, diesel::sql_types::Text, diesel::sql_types::Text, RoleType, SexType): load_dsl::private::CompatibleType<SelectEntity, _> is not satisfied
--> src\db\db_connection.rs:35:35
|
35 | .load::(&mut self.connection)
| ---- ^^^^^^^^^^^^^^^^^^^^ the trait load_dsl::private::CompatibleType<SelectEntity, _> is not implemented for (Integer, diesel::sql_types::Text, diesel::sql_types::Text, RoleType, SexType), which is required by table: LoadQuery<'_, _, SelectEntity>

in

diesel::insert_into(schema::gql_entity::table)
            .values(&new_entity)
            .execute(&mut self.connection)

have error:
error[E0277]: the trait bound &NewEntity<'_>: diesel::Insertable<table> is not satisfied
--> src\db\db_connection.rs:28:21
|
28 | .values(&new_entity)
| ------ ^^^^^^^^^^^ the trait diesel::Insertable<table> is not implemented for &NewEntity<'_>

It looks like the problem is that you are implementing …Sql<Integer, Pg> for Sex, but …Sql<…RoleType, Pg> for Role. First one should not be Integer, it should be SexType like you do with RoleType.

More specifically error is there because diesel::table definition specifies RoleType and SexType so types used by NewEntity must implement From/ToSql with those specific types and this is what errors are essentially about. Unfortunately error messages cannot point out that one type which is a problem due to how diesel is implemented.

The example which uses Integer SQL type for enum is likely for enums which exist only on Rust side, though I do not see this clearly described.

Just as a headsup: This question was cross posted to the diesel support forum as well: Enum in Diesel ORM with implemented FromSql and ToSql · diesel-rs/diesel · Discussion #4280 · GitHub

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.