如何通过 Kotlin 暴露的 ORM 使用 Postgresql 枚举类型?

问题描述 投票:0回答:5

我已将一些 Postgresql 类型添加到 Exposed 作为扩展。它有两种现成类型,名为

enumeration
enumerationByName
。我测试了它们,但没有成功将 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
postgresql enums orm kotlin kotlin-exposed
5个回答
15
投票

您应该声明状态栏如下:

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
}

在新版本中:

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

4
投票

我意识到这是一个老问题,但如果有人仍在寻找答案,你可以去:

Expose 有一个

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

请参阅此处了解更多信息以及非 Postgres 数据库


1
投票

虽然其他答案很有用,但没有人展示一种基于 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
    )
}

我还发布了一个包含此代码的库:https://github.com/PerfectDreams/ExposePowerUtils


0
投票

我创建了这个扩展函数来与 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")

0
投票

不带参数使用,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()
© www.soinside.com 2019 - 2024. All rights reserved.