All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.exposed.dao.References.kt Maven / Gradle / Ivy

There is a newer version: 0.57.0
Show newest version
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