【问题标题】:How to use Postgresql enum type via Kotlin Exposed ORM?如何通过 Kotlin Exposed ORM 使用 Postgresql 枚举类型?
【发布时间】:2017-08-16 22:13:05
【问题描述】:

我已将一些 Postgresql 类型作为扩展添加到 Exposed。它有两个现成的类型,名为enumerationenumerationByName。我对它们都进行了测试,但没有成功将 postgre 枚举类型映射到 Kotlin 枚举类。在读取和写入时都会引发错误

enum class TicketStatus(val status: String) {
    Open("open"),
    Close("close"),
    InProgress("in_progress")
}

class Ticket(id: EntityID<UUID>) : Entity<UUID>(id) {
    companion object : EntityClass<UUID, Ticket>(Tickets)

    var geom by Tickets.geom
    var description by Tickets.description
    var status by Tickets.status
    var createdAt by Tickets.createdAt
    var updatedAt by Tickets.updatedAt
    var owner by Tickets.owner
}

阅读时:

java.lang.IllegalStateException: open is not valid for enum TicketStatus

【问题讨论】:

  • 可能是区分大小写?尝试将元素重命名为openclose 等,或者从数据库中读取一些大写的名称。
  • @hotkey 在问题之前就是这样!不工作!
  • 您的“Tickets”对象在哪里?你如何声明身份?

标签: postgresql enums orm kotlin kotlin-exposed


【解决方案1】:

您应该如下声明状态列:

object Tickets: Table() {
   val status = enumeration("status", TicketStatus::class.java) // will create integer column
   val status = enumerationByName("status", TicketStatus::class.java) // will create varchar with TicketStatus names
}

【讨论】:

  • 失败,“... of org.postgresql.util.PGobject is not valid for enum ...”
  • @Tapac 似乎使用较新版本的 Expose,您应该只使用 TicketStatus::class ,至少对我来说添加 .java 不起作用。
【解决方案2】:

我知道这是一个老问题,但如果有人仍在寻找答案,请前往:

Exposed 有一个 customEnumeration 可用于处理枚举,如果您使用字符串或其他非默认枚举器来支持您的枚举,则特别有用。

对于 postgres,您首先需要像这样定义一个类:

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

然后在您的表定义中,使用以下内容来定义您的列,并根据需要替换占位符:

val enumColumn = customEnumeration(
            "ENUM_COLUMN",
            "ENUM_SCHEMA.ENUM_TYPE",
            { value ->
                when (value) {
                    is PGobject -> LocalEnumClass.valueOf(value.value)
                    is String -> LocalEnumClass.valueOf(value)
                    else -> error("Can't convert ENUM_COLUMN")
                }
            },
            { PGEnum("ENUM_SCHEMA.ENUM_TYPE", it) }
)

在我这样做之前,我遇到了同样的org.postgresql.util.PGobject is not valid for enum

See here for more information, and for non-Postgres databases

【讨论】:

    【解决方案3】:

    我创建了这个扩展函数来处理 postgres 枚举:

    inline fun <reified T : Enum<T>> Table.postgresEnumeration(
       columnName: String,
       postgresEnumName: String
    ) = customEnumeration(columnName, postgresEnumName,
        { value -> enumValueOf<T>(value as String) }, { PGEnum(postgresEnumName, it) })
    

    那么你可以这样使用:

     val theEnumColum = postgresEnumeration<YourEnumClass>("your_cloumn_name",
     "your_postgres_enum_name")
    

    【讨论】:

      【解决方案4】:

      虽然其他答案很有用,但没有人展示一种基于 Kotlin 枚举自动创建和更新枚举的方法。枚举将作为类的名称存储在 PostgreSQL 中,但使用小写。值将与它们的 Java/Kotlin 对应项具有相同的名称。

      /**
       * Creates and updates a PostgreSQL enum based on a Kotlin enum
       *
       * **This does not remove values from the PostgreSQL enum, and it does not insert new enums based on order!**
       *
       * @param enumValues a callback that provides a list of the valid enum values
       */
      inline fun <reified T : Enum<T>> Transaction.createOrUpdatePostgreSQLEnum(enumValues: Array<T>) {
          val valueNames = enumValues.map { it.name }
          val clazzName = T::class.simpleName!!
          val psqlType = clazzName.lowercase()
          val joined = valueNames.joinToString { "'$it'" }
      
          val alreadyInsertedEnumValues = mutableSetOf<String>()
      
          exec("SELECT  n.nspname AS enum_schema,  \n" +
                  "        t.typname AS enum_name,  \n" +
                  "        e.enumlabel AS enum_value\n" +
                  "FROM    pg_type t JOIN \n" +
                  "        pg_enum e ON t.oid = e.enumtypid JOIN \n" +
                  "        pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n" +
                  "WHERE   t.typname = '$psqlType'") {
              while (it.next())
                  alreadyInsertedEnumValues.add(it.getString("enum_value"))
          }
      
          val missingEnums = valueNames.filter { it !in alreadyInsertedEnumValues }
      
          if (alreadyInsertedEnumValues.isEmpty()) {
              exec("CREATE TYPE ${clazzName.lowercase()} AS ENUM ($joined);")
          } else if (missingEnums.isNotEmpty()) {
              for (missingEnum in missingEnums) {
                  exec("ALTER TYPE ${clazzName.lowercase()} ADD VALUE '$missingEnum';")
              }
          }
      }
      
      inline fun <reified T : Enum<T>> Table.postgresEnumeration(
          columnName: String
      ) = customEnumeration(columnName, T::class.simpleName!!.lowercase(),
          { value -> enumValueOf<T>(value as String) }, { PGEnum(T::class.simpleName!!.lowercase(), it) })
      
      // From https://github.com/JetBrains/Exposed/wiki/DataTypes
      class PGEnum<T : Enum<T>>(enumTypeName: String, enumValue: T?) : PGobject() {
          init {
              value = enumValue?.name
              type = enumTypeName
          }
      }
      

      你可以像这样在你的表中使用它:

      object EnumExample : LongIdTable() {
          val type = postgresEnumeration<AchievementType>("type")
      }
      

      请记住,您需要在创建使用枚举的表之前创建枚举!

      transaction {
          createOrUpdatePostgreSQLEnum(AchievementType.values())
      
          SchemaUtils.createMissingTablesAndColumns(
              EnumTable
          )
      }
      

      【讨论】:

        【解决方案5】:

        不带参数使用,postgres和H2数据库

        val db = Database.connect(.....)
        
        val isPg get() = db.dialect.name == "postgresql"
        val isH2 get() = db.dialect.name == "h2"
        
        inline fun <reified T : Enum<T>> Table.customEnum(): Column<T> {
            val columnName = T::class.simpleName?.lowercase()!!
            val sqlPg = "${T::class.simpleName?.lowercase()}_type"
            val sqlH2 = "ENUM(${enumValues<T>().joinToString { "'${it.name}'" }})"
            val fromDb: (Any) -> T = { enumValueOf(it as String) }
            return when {
                isPg -> customEnumeration(columnName, sqlPg, fromDb) { PGEnum(sqlPg, it) }
                isH2 -> customEnumeration(columnName, sqlH2, fromDb) { it.name }
                else -> throw IllegalArgumentException("Unknown db")
            }
        }
        

        使用

        val status = customEnum<Status>()
        

        val status:Column<Status> = customEnum()
        

        在 postgres 中添加枚举类型

        class BuilderEnum {
            private val list = mutableListOf<String>()
            inline fun <reified T : Enum<T>> addEnumType() = addEnumType(T::class)
            fun <T : Enum<T>> addEnumType(c: KClass<T>) = apply {list.add(addEnumTypeExecSql(c)) }
            fun build() = transaction { list.forEach { exec(it) } }
        }
        
        fun <T : Enum<T>> addEnumTypeExecSql(enum: KClass<T>): String {
            val enumTypeName = "${enum.simpleName?.lowercase()}_enum_type"
            return """
                DO $$
                BEGIN
                IF NOT EXISTS (SELECT * FROM pg_type WHERE typname = '$enumTypeName') THEN
                  CREATE TYPE $enumTypeName AS ENUM (${enum.java.enumConstants.joinToString { "'${it}'" }});
                END IF;
                END $$;
            """.trimIndent()
        }
        

        使用

        BuilderEnum()
            .addEnumType<Role>()
            .addEnumType<Status>()
            .build()
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-11
          • 2021-12-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多