Diesel で Enum を使いたい時 (2018年版)

やりたい事

  • Dieselでカラムの型に Enum を使いたい
  • diesel 1.3.3 で有効

以下のブログで、カラムの型に Enum を使いつつ DB の Integer にマッピングするマクロが紹介されています。

https://keens.github.io/blog/2017/12/16/dieselshounetashuu/

しかし diesel 1.3.3 ではこの記事の頃とはインターフェイスが変わっているようで、少し修正が必要でした。 なので 2018 年版の方法としてメモしておきます。

方法

記事のマクロと基本は一緒ですが、以下の変化があります:

  • FromSqlRowが derive できるようになった。
  • AsExpressionが derive できるようになった。
  • diesel::typesモジュールが 1.1.0 から deprecated になった。

FromSqlRowの実装が不要になった分、少し短くなりました。

macro_rules! define_enum {
    (
        $(#[$meta:meta])*
        pub enum $name:ident { $($variant:ident = $val:expr,)* }
    ) => {
        use diesel::sql_types::Integer;
        use diesel::serialize::ToSql;
        use diesel::deserialize::FromSql;

        // 元の enum を必要な derive とともに定義
        $(#[$meta])*
        #[derive(FromSqlRow, AsExpression)]
        #[sql_type = "Integer"]
        pub enum $name {
            $($variant = $val,)*
        }

        // `ToSql`を定義
        impl<DB: diesel::backend::Backend> ToSql<Integer, DB> for $name {
            fn to_sql<W: std::io::Write>(
                &self,
                out: &mut diesel::serialize::Output<W, DB>,
            ) -> Result<diesel::serialize::IsNull, Box<std::error::Error + Send + Sync>> {
                ToSql::<Integer, DB>::to_sql(&(*self as i32), out)
            }
        }

        // `FromSql`を定義
        impl<DB: diesel::backend::Backend> FromSql<Integer, DB> for $name
        where
            i32: FromSql<Integer, DB>,
        {
            fn from_sql(
                bytes: Option<&DB::RawValue>,
            ) -> Result<Self, Box<std::error::Error + Send + Sync>> {
                use self::$name::*;

                match <i32 as FromSql<Integer, DB>>::from_sql(bytes)? {
                    $($val => Ok($variant),)*
                    s => Err(format!("invalid {} value: {}", stringify!($name), s).into()),
                }
            }
        }
    }
}

元記事ではQueryableも impl してましたが、これも不要になったようです。

使用例 (元記事と同じ):

define_enum! {
    #[derive(Debug, Clone, Copy)]
    pub enum Visibility {
        Public = 0,
        Limited = 1,
        Private = 2,
    }
}

#[derive(Queryable)]
pub struct Post {
    pub id: PostId,
    pub title: String,
    pub body: String,
    pub published: Visibility,
}