-
-
Notifications
You must be signed in to change notification settings - Fork 269
Milestone
Description
Hi guys. We've just encountered quite a problem when trying to override Query.getId() in kotlin. Since the whole Query class is marked with @NullMarked, all values are automatically considered @NonNull, but in some cases Query.getId() actually does return null, which is a problem, because
- if we override it as
override fun getId(): Any = query.getId()we get aNullPointerExceptionwhenquery.getId()returnsnull, but since the kotlin compiler understands jspecify annotations, it doesn't allow us to override it asfun getId(): Any?because "Return type of 'getId' is not a subtype of the return type of the overridden member 'fun getId(): Any'".
Example code
// the problematic code
class QueryWrapper<T : Any>(
private val query: Query<T>,
private val databaseService: DatabaseService,
) : SpiQuery<T> {
override fun getId(): Any = query.getId() // <---
override fun findList(): List<T> = withExceptionHandling { query.findList() }
// ...
private fun <T> withExceptionHandling(operation: () -> T): T = try {
operation()
} catch (e: Exception) {
if (e is PersistenceException || e is SQLException) {
databaseService.setStatus(ServiceStatus.Broken(e))
}
throw e
}
}
// test entities
@Entity
@Suppress("unused")
class TestObject {
@Id
val id: String = UUID.randomUUID().toString()
}
@Entity
@Suppress("unused")
class TestConnector {
@Id
val id: String = UUID.randomUUID().toString()
private var sourceId: Long? = null
private var targetId: Long? = null
@JsonIgnore
@ManyToOne
@JoinColumn(insertable = false, updatable = false)
var source: TestObject? = null
@JsonIgnore
@ManyToOne(optional = false)
@JoinColumn(insertable = false, updatable = false)
var target: TestObject? = null
}
// test
class DatabaseServiceTest {
private val dsConfig = DataSourceConfig().also {
it.url = "jdbc:postgresql://localhost:123/xxx"
it.driver = "org.postgresql.Driver"
it.username = "xxx"
it.password = "xxx"
it.platform = Platform.POSTGRES.name.lowercase()
it.isFailOnStart = false
}
private val dbConfig = DatabaseConfig().also {
it.isDefaultServer = false
it.databasePlatformName = dsConfig.platform
it.setSkipDataSourceCheck(true)
it.setDataSourceConfig(dsConfig)
it.addClass(TestObject::class.java)
it.addClass(TestConnector::class.java)
}
private val service = create(dbConfig).asService()
@Test
fun `getId works`() {
val objectQuery = QTestObject(service).where().id.eq("1")
val connectionQuery = QTestConnector(service).where()
.sourceId.isIn(objectQuery.select(QTestObject._alias.id).query())
// this fails with "getId(...) must not be null"
connectionQuery.findList()
}
}If I am not mistaken, this would be fixed if @Nullable annotation was added to the getId() method.