https://kotlinlang.org logo
#exposed
Title
# exposed
a

Adrian Devezin

02/01/2022, 5:11 PM
The following code produces a
Can't init value outside the transaction
, but its in a transaction. Has anyone encountered this before?
Copy code
override suspend fun createUser(
        name: String,
        username: String,
        bio: String,
        igUsername: String?,
        tiktokUsername: String?,
        imageUrl: String,
        imageWidth: Int,
        imageHeight: Int
    ): User = dbQuery {
        val avatar = Image.new(UUID.randomUUID()) {
            url = imageUrl
            height = imageHeight
            width = imageWidth
        }
        val user = User.new(UUID.randomUUID()) {
            this.name = name
            this.username = username
            this.bio = bio
            this.igUsername = igUsername
            this.tiktokUsername = tiktokUsername
            this.avatar = avatar
        }
        return@dbQuery user
    }
Copy code
internal suspend fun <T> dbQuery(block: () -> T): T =
    withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        transaction {
            if (System.getenv("environment").contentEquals("debug", ignoreCase = true)) {
                addLogger(StdOutSqlLogger)
            }
            block()
        }
    }
e

Emil Kantis

02/01/2022, 7:51 PM
I think you need to use suspendedTransaction with coroutines
a

Adrian Devezin

02/01/2022, 7:57 PM
@Emil Kantis Does
newSuspendedTransaction
work in this case to replace
transaction
or should I do this?
Copy code
internal suspend fun <T> dbQuery(block: () -> T): T =
    withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        transaction {
            if (System.getenv("environment").contentEquals("debug", ignoreCase = true)) {
                addLogger(StdOutSqlLogger)
            }
            suspend { 
                suspendedTransaction { 
                    block()
                }
            }
        }.invoke()
    }
e

Emil Kantis

02/01/2022, 8:31 PM
I think the former, but I have only dabbled briefly using exposed + coroutines. It makes sense though - that a transaction could be bound to the current thread while the coroutine is not, and once the coroutine resumes execution on another thread you would run into issues..
a

Adrian Devezin

02/01/2022, 8:44 PM
Unfortunately, neither worked
e

Emil Kantis

02/01/2022, 8:47 PM
Tried this?
Copy code
internal suspend fun <T> dbQuery(block: () -> T): T =
    newSuspendedTransaction(<http://Dispatchers.IO|Dispatchers.IO>) {
            if (System.getenv("environment").contentEquals("debug", ignoreCase = true)) {
                addLogger(StdOutSqlLogger)
            }
            block()
    }
a

Adrian Devezin

02/01/2022, 10:08 PM
Actually I found the error
Its something to do with with converting the DAO model into a new object. I use an extension function called toPresentable to do it. It seems to be tyring to mutate a value even though I am only using getters
Copy code
class User(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<User>(UsersTable)
    var name by UsersTable.name
    var username by UsersTable.username
    var bio by UsersTable.bio
    var igUsername by UsersTable.igUrl
    var tiktokUsername by UsersTable.tikTokUrl
    val posts by Post referrersOn PostsTable.author
    var avatar by Image referencedOn  UsersTable.image
}
Copy code
internal data class UserPresentable(
    val uuid: String,
    val username: String,
    val name: String,
    val bio: String,
    val igUsername: String?,
    val tiktokUsername: String?,
    val avatar: ImagePresentable,
) {
    data class ImagePresentable(val url: String, val height: Int, val width: Int)
}
Copy code
internal fun Image.toPresentable(): UserPresentable.ImagePresentable {
    return UserPresentable.ImagePresentable(
        url = url,
        height = height,
        width = width
    )
}

internal fun User.toPresentable(): UserPresentable {
    return UserPresentable(
        uuid = id.value.toString(),
        username = username,
        tiktokUsername = tiktokUsername,
        igUsername = igUsername,
        name = name,
        bio = bio,
        avatar = avatar.toPresentable(),
    )
}
@Emil Kantis
e

Emil Kantis

02/01/2022, 10:13 PM
Okay.. could be related to Exposed needing to load more data when you try to convert it (maybe
avatar.toPresentable()
forces it to load another entity that wasn’t fetched in the initial query?)
a

Adrian Devezin

02/02/2022, 3:14 PM
That’s exactly it. Thank you! I am not sure why exposed would be setup this way. I am going to open an issue.
120 Views