Skip to content

Commit

Permalink
fix: EXPOSED-704 ClassCastException when referencing an eager-loaded …
Browse files Browse the repository at this point in the history
…backReferencedOn

with keepLoadedReferencesOutOfTransaction = true

Both BackReference and OptionalBackReference classes use Referrers as their delegate
class when getValue() is invoked. This means that, when there is no transaction context,
the delegate correctly attempts to get a child value from the cache, but incorrectly
assumes it will be a SizedIterable (normal for Referrers).

This ensures that an attempt is made to store the child value as a LazySizedCollection
if it is not found to be a collection itself already.
  • Loading branch information
bog-walk committed Jan 31, 2025
1 parent 7e3a92e commit 256a381
Show file tree
Hide file tree
Showing 3 changed files with 9 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ open class Referrers<ParentID : Any, in Parent : Entity<ParentID>, ChildID : Any
}
val transaction = TransactionManager.currentOrNull()
return when {
transaction == null -> thisRef.getReferenceFromCache(reference)
transaction == null -> {
val cachedValue = thisRef.getReferenceFromCache<Any?>(reference)
cachedValue as? SizedIterable<Child> ?: LazySizedCollection(SizedCollection(cachedValue as Child))
}
cache -> {
transaction.entityCache.getOrPutReferrers(thisRef.id, reference, query).also {
thisRef.storeReferenceInCache(reference, it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class EntityReferenceCacheTest : DatabaseTestsBase() {
}

@Test
fun `test backReferencedOn works out of transaction via warmup`() {
fun `test backReferencedOn & optionalBackReferencedOn work out of transaction via load`() {
var y1: EntityTestsData.YEntity by Delegates.notNull()
var b1: EntityTestsData.BEntity by Delegates.notNull()
executeOnH2(EntityTestsData.XTable, EntityTestsData.YTable) {
Expand All @@ -106,14 +106,16 @@ class EntityReferenceCacheTest : DatabaseTestsBase() {
}
}
assertFails { y1.b }
assertFails { y1.bOpt }

transaction(dbWithCache) {
y1.refresh()
b1.refresh()
y1.load(EntityTestsData.YEntity::b)
y1.load(EntityTestsData.YEntity::b, EntityTestsData.YEntity::bOpt)
}

assertEquals(b1.id, y1.b?.id)
assertEquals(b1.id, y1.bOpt?.id)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ object EntityTestsData {
class YEntity(id: EntityID<String>) : Entity<String>(id) {
var x by YTable.x
val b: BEntity? by BEntity.backReferencedOn(XTable.y1)
val bOpt by BEntity optionalBackReferencedOn XTable.y1

companion object : EntityClass<String, YEntity>(YTable)
}
Expand Down

0 comments on commit 256a381

Please sign in to comment.