たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

ExposedでEnumを使ってみる

PostgreSQLをデータベースと使用しているときに、一部のカラムをEnumとして表現したい。

そのためにはまず、Enumを型として定義する必要があるので作成してみる。これはテーブルの定義よりも前にやっておく。

こんな感じ。

class PGEnum<T : Enum<T>>(enumTypeName: String, enumValue: T?) : PGobject() {
    init {
        value = enumValue?.name
        type = enumTypeName
    }
}

カラムの値として受け取りたい値をenum classを使って定義する。

enum class Service {
    INSTAGRAM,
    TWITTER,
}

この二つを使って、テーブルを定義するとこんな感じになる。

第三引数と第四引数では、いわゆるfrom/to変換関数を渡している。

object Posts : UUIDTable() {
    val service = customEnumeration("service", "ServiceEnum", { value -> Service.valueOf(value as String) }, { PGEnum("ServiceEnum", it) })
}

SchemaUtils.create()を呼び出すときにexec()メソッドを呼び出して、CREATE TYPEを実行することで、新しいデータ型を定義する。

CREATE TYPEで指定するのは、customEnumeration()の呼び出し時にsql引数として渡した引数と同じ値。ここではServiceEnumを指定している。

AS ENUMで指定するのは、enum classで定義した内容と同じ値。

val database = Database.connect(
    url = jdbcURL,
    driver = driverClassName,
    user = user,
    password = password
)
transaction(database) { 
    exec(
        "DO $$ BEGIN " +
        "CREATE TYPE ServiceEnum AS ENUM ('INSTAGRAM', 'TWITTER'); " +
      "EXCEPTION " +
        "WHEN duplicate_object THEN null; " +
      "END $$;"
    )
  SchemaUtils.create(Posts)
}

ここで、注意しないといけないのが例外処理を行なっている点。

はじめて実行するときは、データ型がまだ定義されていない状態なので次のように書いたとしてもエラーは発生しない。

exec("CREATE TYPE ServiceEnum AS ENUM ('INSTAGRAM', 'TWITTER');")

ところが、2回目以降になると次のようなエラーが発生してしまう。

Exception in thread "main" org.jetbrains.exposed.exceptions.ExposedSQLException: org.postgresql.util.PSQLException: ERROR: type "serviceenum" already exists

これはすでに存在するデータ型を作成しようとしたために発生した例外。 なので、上記のコードではEXCEPTIONを使って例外処理を行いエラーの発生を制御している。

参考

DataTypes · JetBrains/Exposed Wiki · GitHub

Check if a user-defined type already exists in PostgreSQL - Stack Overflow

Exposed(DAO/DSL) で PostgreSQL からあれこれ SELECT するサンプル・覚書 - インドカレーファンクラブ