EDIT: Updated with a simpler example.
According to the documentation generics and JDBI work together, and I've seen this work before (albeit with an older version of JDBI), but for some reason I now get an error:
org.jdbi.v3.sqlobject.UnableToCreateSqlObjectException: interface Bar has ambiguous methods [public abstract java.lang.Object Foo.baz(java.util.UUID), public abstract java.util.UUID Bar.baz(java.util.UUID)], please resolve with an explicit override
http://jdbi.org/#_annotations_and_inheritance
I have tried @Override for good measure but that didn't change anything.
interface Foo<T> {
@SqlQuery
fun baz(a: UUID): T
}
interface Bar : Foo<UUID> {
@SqlQuery("SELECT ClientID " +
"FROM ClientInstance " +
"WHERE ClientInstanceID = ?")
override fun baz(a: UUID): UUID
}
JDBI.inTransactionUnchecked { handle ->
val tester = handle.attach(Bar::class.java)
val hest = tester.baz(UUID.fromString("7a949c0e-28db-424a-a449-b118e5175a3b"))
LOGGER.info { "hest $hest" }
}
Hi Tor,
We tested that a very similar construction works in vanilla Java: https://github.com/jdbi/jdbi/blob/master/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestSqlObject.java#L293-L307
Perhaps we missed a case for Kotlin. We should add a test corresponding to your use here and fix whatever bug is uncovered.
Ah, looking more closely: note that the return types are different, Object
vs UUID
. I expect this means the Kotlin compiler is emitting a synthetic bridge method that we are then not handling correctly.
For people searching Google, this bug yields a different error message when the parent interface is not annotated (it took me a while to find this!)
Exception in thread "main" java.lang.IllegalStateException: Method StringRepo.get must be default or be annotated with a SQL method annotation.
at org.jdbi.v3.sqlobject.SqlObjectFactory.lambda$buildMethodHandler$15(SqlObjectFactory.java:148)
...
interface Repo<T> {
fun get(): T
}
interface StringRepo : Repo<String> {
@SqlQuery("SELECT 'Hello world!'")
override fun get(): String
}
//...snip
jdbi.onDemand(StringRepo::class.java).get()
@LittleMikeDev, that's a slightly different problem. I've improved the error messaging to not be misleading about the class that is missing the annotation: https://github.com/jdbi/jdbi/pull/1590/files#diff-75b1c04d402bd8924262c7fececfcdb1L166
@TorRanfelt, I dug a bit more into this and it is not obvious what to do to fix it. It looks like the Kotlin compiler is not emitting a bridge method for the covariant method specialization. I filed an issue on the Kotlin bug tracker: https://youtrack.jetbrains.com/issue/KT-33634
As a workaround, Jdbi could emulate the needed bridge method. The code needed probably ends up being small but very tricky to write. I hope Kotlin will at least evaluate the issue soon.
@stevenschlansker Thanks for looking into it.
Kotlin must have had this bridge before because I have seen it work. - This explains why I couldn't get it to work again by reverting to older versions of JDBI.
It looks like this was a compromise to maintain Java 6 compatibility: 6 did not support bridge methods in interfaces, so Kotlin does not emit them. That sounds mighty silly nowadays but probably made sense 10 years ago ;) Hopefully they drop 6 support soon, or at least conditionally enable compatibility with the post-6 world.
Most helpful comment
Hi Tor,
We tested that a very similar construction works in vanilla Java: https://github.com/jdbi/jdbi/blob/master/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestSqlObject.java#L293-L307
Perhaps we missed a case for Kotlin. We should add a test corresponding to your use here and fix whatever bug is uncovered.