JetBrains / Exposed

Kotlin SQL Framework
http://jetbrains.github.io/Exposed/
Apache License 2.0
8.3k stars 691 forks source link

0.39.1 breaks my application #1588

Closed pindab0ter closed 2 years ago

pindab0ter commented 2 years ago

When updating from 0.38.2 to 0.39.1 or .2 my application breaks.

When I'm trying to load one or more instances of the Farmer DAO from the Farmers DSL, this error occurs:

transaction {
    Farmer.findById("1")
}
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at kotlin.reflect.jvm.internal.calls.CallerImpl$Constructor.call(CallerImpl.kt:41)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at org.jetbrains.exposed.dao.EntityClass$entityCtor$1.invoke(EntityClass.kt:32)
    at org.jetbrains.exposed.dao.EntityClass$entityCtor$1.invoke(EntityClass.kt:32)
    at org.jetbrains.exposed.dao.EntityClass.createInstance(EntityClass.kt:229)
    at org.jetbrains.exposed.dao.EntityClass.wrap(EntityClass.kt:233)
    at org.jetbrains.exposed.dao.EntityClass.wrapRow(EntityClass.kt:138)
    at org.jetbrains.exposed.dao.EntityClass$wrapRows$1.invoke(EntityClass.kt:125)
    at org.jetbrains.exposed.dao.EntityClass$wrapRows$1.invoke(EntityClass.kt:124)
    at org.jetbrains.exposed.sql.IterableExKt$mapLazy$1$iterator$1.next(IterableEx.kt:131)
    at kotlin.collections.CollectionsKt___CollectionsKt.firstOrNull(_Collections.kt:272)
    at org.jetbrains.exposed.dao.EntityClass.findById(EntityClass.kt:56)
    at org.jetbrains.exposed.dao.EntityClass.findById(EntityClass.kt:47)
    at nl.pindab0ter.eggbot.utilities.ScratchKt$main$2$1$1$1$result$1.invoke(Scratch.kt:24)
    at nl.pindab0ter.eggbot.utilities.ScratchKt$main$2$1$1$1$result$1.invoke(Scratch.kt:23)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.inTopLevelTransaction$run(ThreadLocalTransactionManager.kt:189)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.access$inTopLevelTransaction$run(ThreadLocalTransactionManager.kt:1)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt$inTopLevelTransaction$1.invoke(ThreadLocalTransactionManager.kt:215)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.keepAndRestoreTransactionRefAfterRun(ThreadLocalTransactionManager.kt:223)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.inTopLevelTransaction(ThreadLocalTransactionManager.kt:214)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt$transaction$1.invoke(ThreadLocalTransactionManager.kt:165)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.keepAndRestoreTransactionRefAfterRun(ThreadLocalTransactionManager.kt:223)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:135)
    at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:132)
    at nl.pindab0ter.eggbot.utilities.ScratchKt$main$2$1$1.invokeSuspend(Scratch.kt:23)
    at nl.pindab0ter.eggbot.utilities.ScratchKt$main$2$1$1.invoke(Scratch.kt)
    at nl.pindab0ter.eggbot.utilities.ScratchKt$main$2$1$1.invoke(Scratch.kt)
    at com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder$HooksBuilder.runBeforeExtensionsAdded(ExtensibleBotBuilder.kt:942)
    at com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder.build$suspendImpl(ExtensibleBotBuilder.kt:478)
    at com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder$build$1.invokeSuspend(ExtensibleBotBuilder.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
    at kotlinx.coroutines.ResumeOnCompletion.invoke(JobSupport.kt:1398)
    at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520)
    at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323)
    at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240)
    at kotlinx.coroutines.JobSupport.continueCompleting(JobSupport.kt:935)
    at kotlinx.coroutines.JobSupport.access$continueCompleting(JobSupport.kt:27)
    at kotlinx.coroutines.JobSupport$ChildCompletion.invoke(JobSupport.kt:1155)
    at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520)
    at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323)
    at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240)
    at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:906)
    at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:863)
    at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:100)
    at kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner.resumeWith(DebugProbesImpl.kt:545)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Caused by: java.lang.ExceptionInInitializerError
    at nl.pindab0ter.eggbot.model.database.Coop$Companion.<init>(Coop.kt:40)
    at nl.pindab0ter.eggbot.model.database.Coop$Companion.<init>(Coop.kt)
    at nl.pindab0ter.eggbot.model.database.Coop.<clinit>(Coop.kt:38)
    at nl.pindab0ter.eggbot.model.database.Farmer.<init>(Farmer.kt:30)
    ... 65 more
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.text.StringsKt__StringsKt.substringAfter, parameter <this>
    at kotlin.text.StringsKt__StringsKt.substringAfter(Strings.kt)
    at kotlin.text.StringsKt__StringsKt.substringAfter$default(Strings.kt:456)
    at org.jetbrains.exposed.sql.Table.getTableNameWithoutScheme$exposed_core(Table.kt:334)
    at org.jetbrains.exposed.sql.Table$PrimaryKey.<init>(Table.kt:424)
    at org.jetbrains.exposed.dao.id.IntIdTable.<init>(IdTable.kt:43)
    at org.jetbrains.exposed.dao.id.IntIdTable.<init>(IdTable.kt:41)
    at nl.pindab0ter.eggbot.model.database.Coops.<init>(Coops.kt:8)
    at nl.pindab0ter.eggbot.model.database.Coops.<clinit>(Coops.kt)
    ... 69 more

The error message doesn't tell me what went wrong specifically. It's not clear to me, at least.

Even though I'm not loading or using this relationship (no eager loading or calling it), it seems to originate in the Coops DSL, with the relation defined like this:

var coops by Coop via CoopFarmers

Unfortunately I can't open source the code. So please let me know what more details I should provide.

The error occurs even when the coops table is completely empty. No non-nullable fields in Farmer are null.

With 3.8.2 this worked without a problem.

AlexeySoshin commented 2 years ago

Seems like Exposed is not able to figure out the name of the table from your table class. Not sure why it only happens in 0.39, as this code wasn't changed in a year: https://github.com/JetBrains/Exposed/blob/2feb5505bb02ef75bf5ddb32395b5fa4c8c7f001/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt#L334

Can you please rename your Coop to CoopTable or try and specify the table explicitly?

Tapac commented 2 years ago

@pindab0ter , can you share your Farmers table declaration and what Java version do you use?

pindab0ter commented 2 years ago

Seems like Exposed is not able to figure out the name of the table from your table class. Not sure why it only happens in 0.39, as this code wasn't changed in a year:

https://github.com/JetBrains/Exposed/blob/2feb5505bb02ef75bf5ddb32395b5fa4c8c7f001/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt#L334

Can you please rename your Coop to CoopTable or try and specify the table explicitly?

The table name was already explicitly defined:

object Coops : IntIdTable() {
    override val tableName = "coops"

@pindab0ter , can you share your Farmers table declaration and what Java version do you use?

The project uses Java version 11 (Azul Zulu 11.0.16, specifically), defined through kotlinOptions.jvmTarget and as the Project SDK in IntelliJ.

This is what Farmers.kt looks like:

import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.ReferenceOption.CASCADE
import org.jetbrains.exposed.sql.jodatime.datetime
import org.joda.time.DateTime.now

object Farmers : IdTable<String>() {
    override val tableName = "farmers"
    override val id = text("id").entityId()
    override val primaryKey = PrimaryKey(id)
    val userId = reference("id", Users, CASCADE, CASCADE)
    // text, integer, double and long fields
    val createdAt = datetime("created_at").clientDefault { now() }
    val updatedAt = datetime("updated_at").clientDefault { now() }
}

I would like to reiterate that version 0.38.2 doesn't have this problem.

The earlier mentioned Coop and Farmer DAOs do have many-to-many relations defined:

// Farmer.kt (DAO)
// ...
    var coops by Coop via CoopFarmers
// ...

// Coop.kt (DAO)
// ...
    var farmers by Farmer via CoopFarmers
// ...

// CoopFarmers.kt (DSL)
object CoopFarmers : Table() {
    override val tableName = "coop_farmers"
    val farmer = reference("farmer", Farmers, CASCADE, CASCADE)
    val coop = reference("coop", Coops, CASCADE, CASCADE)

    init {
        index(true, farmer, coop)
    }
}
Tapac commented 2 years ago

I was able to reproduce the bug, will fix it with next release. As a workaround you can move table name into table declaration like:

object Farmers : IdTable<String>{"farmers") { ... }
object CoopFarmers : Table("coop_farmers")  { ... }

It should help

pindab0ter commented 2 years ago

Awesome! Glad to hear you were able to reproduce the problem.

Thank you for providing a workaround as well. I will give that a go.

Do you want to leave this issue open until there’s a commit/PR/release with a fix, or should I close it?

Tapac commented 2 years ago

I'll close it just after all tests will pass on CI. The fix is on the way.