Lib versions checked: 0.15.1, 0.16.2
Hi :) I've been looking through the existing (mostly closed) issues, but they seem old and only partially related.
Using the DAO API, and following examples in the docs, I've had enums working fine, until trying to set a column (creating and reading records has been ok).
Now, after setting a column value, the transaction flush throws an exception:
[...] PgEnum cannot be cast to java.lang.Enum
Hopefully I'm just doing something wrong, but it seems buried pretty deep in the DAO code.
I can provide more of my code, and more stack trace if it helps :)
My code in question looks like:
object ChallengeTable : IntIdTable() {
val createdAt = datetime("createdAt")
val slug = varchar("slug", 256).uniqueIndex()
val status = ChallengeStatus.pgColumn(this, "status")
val entryId = varchar("entryId", 64)
}
class ExposedChallenge(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<ExposedChallenge>(ChallengeTable)
var createdAt by ChallengeTable.createdAt
var slug by ChallengeTable.slug
var status by ChallengeTable.status
var entryId by ChallengeTable.entryId
}
enum class ChallengeStatus {
draft,
live,
completed,
archive;
companion object {
const val dbName = "challenge_status"
fun pgColumn(table: Table, name: String) = table.customEnumeration(
name = name,
sql = dbName,
fromDb = { it.fromPg() },
toDb = { it.toPg() }
)
private fun Any.fromPg() = valueOf(this as String)
private fun ChallengeStatus?.toPg() = PgEnum(this)
}
class PgEnum(enumValue: ChallengeStatus?) : PGobject() {
init {
value = enumValue?.name
type = dbName
}
}
}
And the call site (simplified):
transaction {
ExposedChallenge
.find { (ChallengeTable.slug eq "exampleSlug") }
.first().apply {
status = challengeStatus
}
}
The first few stack slices:
java.lang.ClassCastException: ChallengeStatus$PgEnum cannot be cast to java.lang.Enum
at org.jetbrains.exposed.sql.Table$customEnumeration$1.notNullValueToDB(Table.kt:235)
at org.jetbrains.exposed.sql.IColumnType$DefaultImpls.nonNullValueToString(ColumnType.kt:51)
at org.jetbrains.exposed.sql.ColumnType.nonNullValueToString(ColumnType.kt:60)
at org.jetbrains.exposed.sql.IColumnType$DefaultImpls.valueToString(ColumnType.kt:43)
at org.jetbrains.exposed.sql.ColumnType.valueToString(ColumnType.kt:60)
at org.jetbrains.exposed.sql.QueryBuilder$registerArguments$1.invoke(Expression.kt:19)
Workaround update: avoiding the DAO DSL with all the same existing classes works :)
i.e.:
transaction {
ChallengeTable.update({ ChallengeTable.slug eq challengeSlug }) {
it[status] = challengeStatus
}
}
What is challengeStatus in a fail example? PgEnum or ChallengeStatus?
Ah, sorry, it's a ChallengeStatus (specifically live in this case).
@abubics, I was trying to reproduce an issue by changing existing customEnumeration test but without any success.
Could you please share a complete sample or patch for the test case?
I'll try, but probably don't have time until after the weekend... I'm moving house on Wednesday 馃槄 thanks!
Facing the same issue. Try the DAO update not the DSL update. Not very hard to reproduce.
@bvjebin, there is test with both DSL and DAO cases. That's why I ask for a reproducable sample
Working on it now :)
I might be wrong, I don't see a transaction in the test. Does that make any difference?
What I have observed is notNullValueToDB of customEnumeration in Table.kt is called twice. The first time it is called, the value argument is an Enum and the second time it is called, it is of type PGEnum which is returned from the customEnumeration I have defined as my column.
It is happening only for update. Not for insert.
Exposed Version: 0.11.2
Transaction Helper
class TransactionHelper {
companion object {
private val logger: Logger = LoggerFactory.getLogger("TransactionHelper")
fun <T> execute(fn: () -> T): T {
return transaction {
try {
addLogger(StdOutSqlLogger)
fn()
} catch (exception: Exception) {
val original = (exception as? ExposedSQLException)?.cause
//TODO: Need to build comprehensive sql error handling based on cause.SQLState value
when (original) {
is SQLIntegrityConstraintViolationException ->
TransactionHelper.logger.error("which one SQL constraint violated")
is BatchUpdateException ->
TransactionHelper.logger.error("SQL constraint violated")
else ->
TransactionHelper.logger.error("Unknown sql exception ${exception.message}")
}
TransactionManager.current().rollback()
throw exception
} catch (error: Error) {
TransactionHelper.logger.error("Error occurred: ${error.message}")
throw error
}
}
}
}
}
In the controller:
val response = TransactionHelper.execute {
val userProfile = UserProfile.findById(id.toInt(10))
if (userProfile != null) {
UserProfileSerializer.serialize(UserProfile.update(userProfile, serializedUserProfile))
} else
throw WebApplicationException(404)
}
In the model:
enum class ActivityLevel { ACTIVE, SEMI_ACTIVE, SEDENTARY }
object UserProfiles : BaseIntIdTable("user_profiles") {
val activityLevel = customEnumeration("activity_level",
"activity_level",
{ ActivityLevel.valueOf(it.toString()) },
{ it -> PGEnum("activity_level", it) }).nullable()
val isDeleted = bool("is_deleted").default(false)
}
class UserProfile(id: EntityID<Int>) : BaseIntEntity(id, UserProfiles) {
companion object : BaseIntEntityClass<UserProfile>(UserProfiles) {
fun update(row: UserProfile, request: UserProfileSerializer): UserProfile {
with(row) {
activityLevel = request.activityLevel
isDeleted = request.isDeleted
}
return row
}
}
var activityLevel by UserProfiles.activityLevel
var isDeleted by UserProfiles.isDeleted
}
This is what I am trying.. See if this helps.
Sorry for the delay, here's a minimal sample :D
https://github.com/abubics/exposed-enum
@abubics , thank you for your sample. It helped a lot and I was able to find the bug.
It will be fixed in upcoming 0.18.1.
Awesome, glad to have been helpful ^_^
Most helpful comment
@abubics , thank you for your sample. It helped a lot and I was able to find the bug.
It will be fixed in upcoming 0.18.1.