org.jetbrains.exposed.dao.References.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exposed-dao Show documentation
Show all versions of exposed-dao Show documentation
Exposed, an ORM framework for Kotlin
package org.jetbrains.exposed.dao
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.SizedIterable
import org.jetbrains.exposed.sql.emptySized
import org.jetbrains.exposed.sql.transactions.TransactionManager
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
private fun checkReference(reference: Column<*>, factoryTable: IdTable<*>) {
val refColumn = reference.referee ?: error("Column $reference is not a reference")
val targetTable = refColumn.table
if (factoryTable != targetTable) {
error("Column and factory point to different tables")
}
}
class Reference, ID : Comparable, out Target : Entity>(
val reference: Column,
val factory: EntityClass
) {
init {
checkReference(reference, factory.table)
}
}
class OptionalReference, ID : Comparable, out Target : Entity>(
val reference: Column,
val factory: EntityClass
) {
init {
checkReference(reference, factory.table)
}
}
internal class BackReference, out Parent : Entity, ChildID : Comparable, in Child : Entity, REF>
(reference: Column, factory: EntityClass) : ReadOnlyProperty {
internal val delegate = Referrers(reference, factory, true)
override operator fun getValue(thisRef: Child, property: KProperty<*>) =
delegate.getValue(thisRef.apply { thisRef.id.value }, property).single() // flush entity before to don't miss newly created entities
}
class OptionalBackReference, out Parent : Entity, ChildID : Comparable, in Child : Entity, REF>
(reference: Column, factory: EntityClass) : ReadOnlyProperty {
internal val delegate = OptionalReferrers(reference, factory, true)
override operator fun getValue(thisRef: Child, property: KProperty<*>) =
delegate.getValue(thisRef.apply { thisRef.id.value }, property).singleOrNull() // flush entity before to don't miss newly created entities
}
class Referrers, in Parent : Entity, ChildID : Comparable, out Child : Entity, REF>
(val reference: Column, val factory: EntityClass, val cache: Boolean) : ReadOnlyProperty> {
init {
reference.referee ?: error("Column $reference is not a reference")
if (factory.table != reference.table) {
error("Column and factory point to different tables")
}
}
override operator fun getValue(thisRef: Parent, property: KProperty<*>): SizedIterable {
val value = thisRef.run { reference.referee()!!.lookup() }
if (thisRef.id._value == null || value == null) return emptySized()
val query = { factory.find { reference eq value } }
val transaction = TransactionManager.currentOrNull()
return when {
transaction == null -> thisRef.getReferenceFromCache(reference)
cache -> {
transaction.entityCache.getOrPutReferrers(thisRef.id, reference, query).also {
thisRef.storeReferenceInCache(reference, it)
}
}
else -> query()
}
}
}
class OptionalReferrers, in Parent : Entity, ChildID : Comparable, out Child : Entity, REF>
(val reference: Column, val factory: EntityClass, val cache: Boolean) : ReadOnlyProperty> {
init {
reference.referee ?: error("Column $reference is not a reference")
if (factory.table != reference.table) {
error("Column and factory point to different tables")
}
}
override operator fun getValue(thisRef: Parent, property: KProperty<*>): SizedIterable {
val value = thisRef.run { reference.referee()!!.lookup() }
if (thisRef.id._value == null || value == null) return emptySized()
val query = { factory.find { reference eq value } }
val transaction = TransactionManager.currentOrNull()
return when {
transaction == null -> thisRef.getReferenceFromCache(reference)
cache -> {
transaction.entityCache.getOrPutReferrers(thisRef.id, reference, query).also {
thisRef.storeReferenceInCache(reference, it)
}
}
else -> query()
}
}
}
private fun > getReferenceObjectFromDelegatedProperty(entity: SRC, property: KProperty1): Any? {
property.isAccessible = true
return property.getDelegate(entity)
}
private fun > filterRelationsForEntity(
entity: SRC,
relations: Array, Any?>>
): Collection> {
val validMembers = entity::class.memberProperties
return validMembers.filter { it in relations } as Collection>
}
@Suppress("UNCHECKED_CAST")
private fun > List>.preloadRelations(
vararg relations: KProperty1, Any?>,
nodesVisited: MutableSet> = mutableSetOf()
) {
val entity = this.firstOrNull() ?: return
if (nodesVisited.contains(entity.klass)) {
return
} else {
nodesVisited.add(entity.klass)
}
val isReferenceCacheEnabled = TransactionManager.currentOrNull()?.db?.config?.keepLoadedReferencesOutOfTransaction ?: false
fun storeReferenceCache(reference: Column<*>, prop: KProperty1, Any?>) {
if (isReferenceCacheEnabled) {
this.forEach { entity ->
entity.storeReferenceInCache(reference, prop.get(entity))
}
}
}
val directRelations = filterRelationsForEntity(entity, relations)
directRelations.forEach { prop ->
when (val refObject = getReferenceObjectFromDelegatedProperty(entity, prop)) {
is Reference<*, *, *> -> {
(refObject as Reference>, *, Entity<*>>).reference.let { refColumn ->
this.map { it.run { refColumn.lookup() } }.takeIf { it.isNotEmpty() }?.let { refIds ->
refObject.factory.find { refColumn.referee>>()!! inList refIds.distinct() }.toList()
}.orEmpty()
storeReferenceCache(refColumn, prop)
}
}
is OptionalReference<*, *, *> -> {
(refObject as OptionalReference>, *, Entity<*>>).reference.let { refColumn ->
this.mapNotNull { it.run { refColumn.lookup() } }.takeIf { it.isNotEmpty() }?.let { refIds ->
refObject.factory.find { refColumn.referee>>()!! inList refIds.distinct() }.toList()
}.orEmpty()
storeReferenceCache(refColumn, prop)
}
}
is Referrers<*, *, *, *, *> -> {
(refObject as Referrers, *, Entity<*>, Any>).reference.let { refColumn ->
val refIds = this.map { it.run { refColumn.referee()!!.lookup() } }
refObject.factory.warmUpReferences(refIds, refColumn)
storeReferenceCache(refColumn, prop)
}
}
is OptionalReferrers<*, *, *, *, *> -> {
(refObject as OptionalReferrers, *, Entity<*>, Any>).reference.let { refColumn ->
val refIds = this.mapNotNull { it.run { refColumn.referee()!!.lookup() } }
refObject.factory.warmUpOptReferences(refIds, refColumn)
storeReferenceCache(refColumn, prop)
}
}
is InnerTableLink<*, *, *, *> -> {
(refObject as InnerTableLink, Comparable>, Entity>>>).let { innerTableLink ->
innerTableLink.target.warmUpLinkedReferences(
references = this.map { it.id },
sourceRefColumn = innerTableLink.sourceColumn,
targetRefColumn = innerTableLink.targetColumn,
linkTable = innerTableLink.table
)
storeReferenceCache(innerTableLink.sourceColumn, prop)
}
}
is BackReference<*, *, *, *, *> -> {
(refObject.delegate as Referrers, *, Entity<*>, Any>).reference.let { refColumn ->
val refIds = this.map { it.run { refColumn.referee()!!.lookup() } }
refObject.delegate.factory.warmUpReferences(refIds, refColumn)
storeReferenceCache(refColumn, prop)
}
}
is OptionalBackReference<*, *, *, *, *> -> {
(refObject.delegate as OptionalReferrers, *, Entity<*>, Any>).reference.let { refColumn ->
val refIds = this.map { it.run { refColumn.referee()!!.lookup() } }
refObject.delegate.factory.warmUpOptReferences(refIds, refColumn)
storeReferenceCache(refColumn, prop)
}
}
else -> error("Relation delegate has an unknown type")
}
}
if (directRelations.isNotEmpty() && relations.size != directRelations.size) {
val remainingRelations = relations.toList() - directRelations
directRelations.map { relationProperty ->
val relationsToLoad = this.flatMap {
when (val relation = (relationProperty as KProperty1, *>).get(it)) {
is SizedIterable<*> -> relation.toList()
is Entity<*> -> listOf(relation)
null -> listOf()
else -> error("Unrecognised loaded relation")
} as List>
}.groupBy { it::class }
relationsToLoad.forEach { (_, entities) ->
entities.preloadRelations(
relations = remainingRelations.toTypedArray() as Array, Any?>>,
nodesVisited = nodesVisited
)
}
}
}
}
fun , SRC : Entity, REF : Entity<*>, L: Iterable> L.with(vararg relations: KProperty1): L {
toList().apply {
if (any { it.isNewEntity() }) {
TransactionManager.current().flushCache()
}
preloadRelations(*relations)
}
return this
}
fun , SRC : Entity> SRC.load(vararg relations: KProperty1, Any?>): SRC = apply {
listOf(this).with(*relations)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy