我已将一些 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
您应该声明状态栏如下:
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
}
我意识到这是一个老问题,但如果有人仍在寻找答案,你可以去:
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
。
虽然其他答案很有用,但没有人展示一种基于 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
我创建了这个扩展函数来与 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")
不带参数使用,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()