There seems to be a bug upgrading to 0.50.0 (and 0...
# exposed
m
There seems to be a bug upgrading to 0.50.0 (and 0.50.1) with the
referencedOn
DAO operator, a table declared like this:
Copy code
object UsersRolesPivotTable(...) {
    val userID = nodeID<UserID>("user_id").references(UserTable.id)
    val roleID = nodeID<RoleID>("role_id").references(RoleTable.id)
}

class UsersRolesPivotEntity(...) {
    ...
    val role by RoleEntity referencedOn UsersRolesPivotTable.roleID
}

// The nodeID function, just registers a column that is just a typed UUID under the hood
inline fun <reified T : NodeID> Table.nodeID(name: String): Column<T> {
    return registerColumn(name, NodeColumnType { T::class.primaryConstructor!!.call(it) })
}
now produces a class cast exception when trying to read the the
role
from the entity.
Copy code
class ai.zylon.backend.core.node.RoleID cannot be cast to class org.jetbrains.exposed.dao.id.EntityID (ai.zylon.backend.core.node.RoleID is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @6999cd39; org.jetbrains.exposed.dao.id.EntityID is in unnamed module of loader 'app')
	at org.jetbrains.exposed.sql.EntityIDColumnType.notNullValueToDB(ColumnType.kt:199)
	at org.jetbrains.exposed.sql.IColumnType$DefaultImpls.valueToDB(ColumnType.kt:39)
	at org.jetbrains.exposed.sql.ColumnType.valueToDB(ColumnType.kt:103)
        ...
It's trying to cast an ID (in my case a
RoleID
) as an
EntityID
. This worked fine in 0.49.0 Modifying the table to use just
reference
fixes the issue but forces me to change types across the whole application which is quite a big refactor, all the plain
T
id types become
EntityID<T>
.
Copy code
object UsersRolesPivotTable(...) {
    val userID = references("user_id", UserTable.id)
    val roleID = references("role_id", RoleTable.id)
}
So essentially,
registerColumn(...).references(...)
breaks the DAO now,
reference(...)
works fine.
c
Hi @minivac Version 0.50.0 introduced breaking changes around column type safety, which affects any custom column types and its associated functions. These will now need to be refactored to account for the stricter type safety. I tried to work backwards to make your example fail, but I'm probably making the wrong assumptions about the missing pieces, because using a more basic custom column type showed no issues with
registerColumn().references()
. To help resolve your issue, please consider giving some more details about: • What is the signature of
NodeColumnType
, particularly what it extends or what
ColumnType<?>
is. • Maybe also what
notNullValueToDB()
or
valueToDB()
looks like. • What
UserTable
and
RoleTable
extend. At minimum, what is the
Column<?>
of
UserTable.id
, for example:
Column<EntityID<UserID>>
? • The same for
NodeID
. Does it implement
Comparable
for example? If you'd prefer to open an issue on YouTrack with a full reproducible example, please feel free to do so as well, so we can assist you further.
m
Thanks for the reply @Chantal Loncle I'll try to reproduce it in a minimal isolated repo, could be a bug in my code after the typing update but the fact that it worked properly in 0.49 makes me think there is something else going on.
c
@minivac Every custom type in my test projects, which worked properly in 0.49.0, started failing when I bumped to 0.50.0. And almost all overrides and signatures required some level of refactoring. This is expected given the nature of the the new type safety changes. Please @ me whenever you are able to reproduce an example. If this is an unexpected side effect of the type changes, I'd be eager to work on a fix in time for the next release. Thanks in advance.
m
@Chantal Loncle got a minimal case reproducing the bug, only using exposed types. It's hard to share the whole project but hopefully you can copy paste these easily in your testbed The two tables, just a
table_a
and
table_b
, b has a reference to
table_a
and uses
val entityA by EntityA referencedOn TableB.refToA
to load it in the EntityB.
Copy code
object TableA : UUIDTable("table_a")

class EntityA(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<EntityA>(TableA)
}

object TableB : UUIDTable("table_b") {
    val refToA = registerColumn("ref_to_a", UUIDColumnType())
        .references(TableA.id)
}

class EntityB(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<EntityB>(TableB)

    var refToA by TableB.refToA
    val entityA by EntityA referencedOn TableB.refToA
}
The SQL to load one of each in the database:
Copy code
CREATE TABLE IF NOT EXISTS table_a
(
    id uuid PRIMARY KEY
);

CREATE TABLE IF NOT EXISTS table_b
(
    id       uuid PRIMARY KEY,
    ref_to_a uuid NOT NULL,
    CONSTRAINT fk_table_b_ref_to_a__id FOREIGN KEY (ref_to_a) REFERENCES table_a (id) ON DELETE RESTRICT ON UPDATE RESTRICT
);

INSERT INTO table_a (id)
VALUES ('00000000-0000-0000-0000-000000000001');
INSERT INTO table_b (id, ref_to_a)
VALUES ('00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000001');
The test:
Copy code
transaction {
                val b = EntityB.all().first()
                println(b.entityA) // <--- this will fail
            }
Copy code
class java.util.UUID cannot be cast to class org.jetbrains.exposed.dao.id.EntityID (java.util.UUID is in module java.base of loader 'bootstrap'; org.jetbrains.exposed.dao.id.EntityID is in unnamed module of loader 'app')
interestingly, this seems to work to with
IntIdTable
, so maybe the bug is on the
UUIDTable
stuff, which I am also using in my project 🤔
more info, the error happens here
Entity.kt
line 132
Copy code
else -> {
                    // @formatter:off
                    factory.findWithCacheCondition({
                        reference.referee!!.getValue(this, desc) == refValue
                    }) {
                        reference.referee<REF>()!! eq refValue
                    }.singleOrNull()?.also {
                        storeReferenceInCache(reference, it)
                    }
                    // @formatter:on
                }
this
reference.referee<REF>()!! eq refValue
expression is used in a
find { ... }
, the
reference.referee<REF>()!!
is correct,
refValue
is an
UUID
also correct. Crash finally happens when it then reaches
ColumnType.kt
EntityIDColumnType<T>::notNullValueToDB
, after the typing update in 0.50.0 the parameter is no longer
Any
but
EntityID<T>
so the
UUID
trying to be cast o an
EntityID<UUID>
simply crashes
c
Thanks for putting a repro together so quickly @minivac! As you mentioned, it works with Int and Long, so I'll get on pushing a fix for UUID to make it start working again. Here's EXPOSED-382 if you're interested in tracking the fix. If you come across any further typing issues with 0.50.0, please don't hesitate to share, either here or on YouTrack, so we can address them sooner. By the way, with the reference/relation setup you detailed above, how do you routinely perform inserts? Using DAO, DSL, or with SQL? If you're willing to share, of course; I was just curious when I was writing the different test cases for this issue.
m
thanks for taking the time to fix it, saw the PR on github (I guess you are bog-walk?). I only use the DAO api for writes, never had the need to use the DSL or plain SQL so far. I isolated the insert test because I wasn't sure if the issue was in the in-memory cache layer of the transaction or somewhere else and this exact case is what was crashing my app after the update.
c
No worries 👍 And thanks for sharing.
m
any ETA for a version with this fix to be published? also ran into https://github.com/JetBrains/Exposed/pull/2070 which is in 0.50.1 but can't bump without the UUID fix
c
Hi @minivac. The fix for this particular issue will be included in the next end-of-month release (0.51.0), expected to be available between May 28 to 31, if all is routine.
thank you color 1