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.CompositeID
import org.jetbrains.exposed.dao.id.CompositeIdTable
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.SqlExpressionBuilder.wrap
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<*>): Map, Column<*>> {
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")
}
return mapOf(reference to refColumn)
}
/**
* Class representing a table relation between two [Entity] classes, which is responsible for
* retrieving the parent entity referenced by the child entity.
*
* @param reference The reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the parent entity referenced by the child entity.
*/
class Reference, ID : Comparable, out Target : Entity>(
val reference: Column,
val factory: EntityClass,
references: Map, Column<*>>? = null
) {
val allReferences = references ?: checkReference(reference, factory.table)
}
/**
* Class representing an optional table relation between two [Entity] classes, which is responsible for
* retrieving the parent entity optionally referenced by the child entity.
*
* @param reference The nullable reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the parent entity optionally referenced by the child entity.
*/
class OptionalReference, ID : Comparable, out Target : Entity>(
val reference: Column,
val factory: EntityClass,
references: Map, Column<*>>? = null
) {
val allReferences = references ?: checkReference(reference, factory.table)
}
/**
* Class responsible for implementing property delegates of the read-only properties involved in a table
* relation between two [Entity] classes, which retrieves the child entity that references the parent entity.
*
* @param reference The reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the child entity that references the parent entity.
*/
internal class BackReference, out Parent : Entity, ChildID : Comparable, in Child : Entity, REF>(
reference: Column,
factory: EntityClass,
references: Map, Column<*>>? = null
) : ReadOnlyProperty {
internal val delegate = Referrers(reference, factory, true, references)
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 responsible for implementing property delegates of the read-only properties involved in an optional table
* relation between two [Entity] classes, which retrieves the child entity that optionally references the parent entity.
*
* @param reference The nullable reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the child entity that optionally references the parent entity.
*/
class OptionalBackReference, out Parent : Entity, ChildID : Comparable, in Child : Entity, REF>(
reference: Column,
factory: EntityClass,
references: Map, Column<*>>? = null
) : ReadOnlyProperty {
internal val delegate = OptionalReferrers(reference, factory, true, references)
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 responsible for implementing property delegates of the read-only properties involved in a one-to-many
* relation, which retrieves all child entities that reference the parent entity.
*
* @param reference The reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the child entity that references the parent entity.
* @param cache Whether loaded reference entities should be stored in the [EntityCache].
*/
open class Referrers, in Parent : Entity, ChildID : Comparable, out Child : Entity, REF>(
val reference: Column,
val factory: EntityClass,
val cache: Boolean,
references: Map, Column<*>>? = null
) : ReadOnlyProperty> {
/** The list of columns and their [SortOrder] for ordering referred entities in one-to-many relationship. */
private val orderByExpressions: MutableList, SortOrder>> = mutableListOf()
val allReferences = references ?: run {
reference.referee ?: error("Column $reference is not a reference")
if (factory.table != reference.table) {
error("Column and factory point to different tables")
}
mapOf(reference as Column<*> to reference.referee!!)
}
@Suppress("UNCHECKED_CAST", "NestedBlockDepth")
override operator fun getValue(thisRef: Parent, property: KProperty<*>): SizedIterable {
val isSingleIdReference = hasSingleReferenceWithReferee(allReferences)
val value: REF = thisRef.run {
if (isSingleIdReference) {
val refereeColumn = reference.referee()!!
val refereeValue = refereeColumn.lookup()
when {
reference.columnType !is EntityIDColumnType<*> && refereeColumn.columnType is EntityIDColumnType<*> ->
(refereeValue as? EntityID<*>)?.let { it.value as? REF } ?: refereeValue
else -> refereeValue
}
} else {
getCompositeID {
allReferences.map { (_, parent) -> parent to parent.lookup() }
} as REF
}
}
if (thisRef.id._value == null || value == null) return emptySized()
val condition = if (isSingleIdReference) {
reference eq value
} else {
value as CompositeID
allReferences.map { (child, parent) ->
val parentValue = value[parent as Column>>].value
EqOp(child, child.wrap((parentValue as? DaoEntityID<*>)?.value ?: parentValue))
}.compoundAnd()
}
val query = {
@Suppress("SpreadOperator")
factory
.find { condition }
.orderBy(*orderByExpressions.toTypedArray())
}
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()
}
}
/** Modifies this reference to sort entities based on multiple columns as specified in [order]. **/
infix fun orderBy(order: List, SortOrder>>) = this.also {
orderByExpressions.addAll(order)
}
/** Modifies this reference to sort entities according to the specified [order]. **/
infix fun orderBy(order: Pair, SortOrder>) = orderBy(listOf(order))
/** Modifies this reference to sort entities by a column specified in [expression] using ascending order. **/
infix fun orderBy(expression: Expression<*>) = orderBy(listOf(expression to SortOrder.ASC))
/** Modifies this reference to sort entities based on multiple columns as specified in [order]. **/
fun orderBy(vararg order: Pair, SortOrder>) = orderBy(order.toList())
}
/**
* Class responsible for implementing property delegates of the read-only properties involved in an optional one-to-many
* relation, which retrieves all child entities that optionally reference the parent entity.
*
* @param reference The nullable reference column defined on the child entity's associated table.
* @param factory The [EntityClass] associated with the child entity that optionally references the parent entity.
* @param cache Whether loaded reference entities should be stored in the [EntityCache].
*/
@Deprecated(
message = "The OptionalReferrers class is a complete duplicate of the Referrers class; therefore, the latter should be used instead.",
replaceWith = ReplaceWith("Referrers"),
level = DeprecationLevel.WARNING
)
class OptionalReferrers, in Parent : Entity, ChildID : Comparable, out Child : Entity, REF>(
reference: Column,
factory: EntityClass,
cache: Boolean,
references: Map, Column<*>>? = null
) : Referrers(reference, factory, cache, references)
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", "NestedBlockDepth", "ComplexMethod", "LongMethod")
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))
}
}
}
fun Entity<*>.getReferenceId(
delegateRefColumn: Column<*>,
refColumns: Map, Column<*>>,
isSingleIdReference: Boolean
): Any? {
return if (isSingleIdReference) {
delegateRefColumn.lookup()
} else {
val childValues = refColumns.keys.map { it.lookup() }
if (childValues.any { it == null }) {
null
} else {
getCompositeID {
refColumns.values.mapIndexed { i, parent -> parent to childValues[i] }
}
}
}
}
fun Entity<*>.getCompositeReferrerId(refColumns: Map, Column<*>>) = getCompositeID {
refColumns.map { (child, parent) -> child to (parent.lookup() as EntityID<*>).value }
}
fun Entity<*>.getRefereeId(refereeColumn: Column<*>, delegateRefColumn: Column<*>): Any {
val refereeValue = refereeColumn.lookup()
return refereeValue.takeUnless {
delegateRefColumn.columnType !is EntityIDColumnType<*> && it is EntityID<*>
} ?: (refereeValue as EntityID<*>).value
}
val directRelations = filterRelationsForEntity(entity, relations)
directRelations.forEach { prop ->
when (val refObject = getReferenceObjectFromDelegatedProperty(entity, prop)) {
is Reference<*, *, *> -> {
(refObject as Reference>, *, Entity<*>>).allReferences.let { refColumns ->
val isSingleIdReference = hasSingleReferenceWithReferee(refColumns)
val delegateRefColumn = refObject.reference
this.map { entity ->
entity.getReferenceId(delegateRefColumn, refColumns, isSingleIdReference) as ID
}.takeIf { it.isNotEmpty() }?.let { refIds ->
val condition = if (isSingleIdReference) {
val castReferee = (delegateRefColumn as Column).referee()!!
val baseReferee = castReferee.takeUnless {
it.columnType is EntityIDColumnType<*> && refIds.first() !is EntityID<*>
} ?: (castReferee.columnType as EntityIDColumnType).idColumn
baseReferee inList refIds.distinct()
} else {
refColumns.values.toList() inList (refIds.distinct() as List)
}
refObject.factory.find(condition).toList()
}.orEmpty()
storeReferenceCache(delegateRefColumn, prop)
}
}
is OptionalReference<*, *, *> -> {
(refObject as OptionalReference>, *, Entity<*>>).allReferences.let { refColumns ->
val isSingleIdReference = hasSingleReferenceWithReferee(refColumns)
val delegateRefColumn = refObject.reference
this.mapNotNull { entity ->
entity.getReferenceId(delegateRefColumn, refColumns, isSingleIdReference) as? ID
}.takeIf { it.isNotEmpty() }?.let { refIds ->
val condition = if (isSingleIdReference) {
(delegateRefColumn as Column).referee()!! inList refIds.distinct()
} else {
refColumns.values.toList() inList (refIds.distinct() as List)
}
refObject.factory.find(condition).toList()
}.orEmpty()
storeReferenceCache(delegateRefColumn, prop)
}
}
is Referrers<*, *, *, *, *> -> {
(refObject as Referrers, *, Entity<*>, Any>).allReferences.let { refColumns ->
val delegateRefColumn = refObject.reference
if (hasSingleReferenceWithReferee(refColumns)) {
val castReferee = delegateRefColumn.referee()!!
val refIds = this.map { entity -> entity.getRefereeId(castReferee, delegateRefColumn) }
refObject.factory.warmUpReferences(refIds, delegateRefColumn)
} else {
val refIds = this.map { it.getCompositeReferrerId(refColumns) }
refObject.factory.warmUpCompositeIdReferences(refIds, refColumns, delegateRefColumn)
}
storeReferenceCache(delegateRefColumn, 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>).allReferences.let { refColumns ->
val delegateRefColumn = refObject.delegate.reference
if (hasSingleReferenceWithReferee(refColumns)) {
val castReferee = delegateRefColumn.referee()!!
val refIds = this.map { entity -> entity.getRefereeId(castReferee, delegateRefColumn) }
refObject.delegate.factory.warmUpReferences(refIds, delegateRefColumn)
} else {
val refIds = this.map { it.getCompositeReferrerId(refColumns) }
refObject.delegate.factory.warmUpCompositeIdReferences(refIds, refColumns, delegateRefColumn)
}
storeReferenceCache(delegateRefColumn, prop)
}
}
is OptionalBackReference<*, *, *, *, *> -> {
(refObject.delegate as OptionalReferrers, *, Entity<*>, Any>).allReferences.let { refColumns ->
val delegateRefColumn = refObject.delegate.reference
if (hasSingleReferenceWithReferee(refColumns)) {
val refIds = this.map { it.run { delegateRefColumn.referee()!!.lookup() } }
refObject.delegate.factory.warmUpOptReferences(refIds, delegateRefColumn)
} else {
val refIds = this.map { it.getCompositeReferrerId(refColumns) }
refObject.delegate.factory.warmUpCompositeIdReferences(refIds, refColumns, delegateRefColumn)
}
storeReferenceCache(delegateRefColumn, 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
)
}
}
}
}
/**
* Eager loads references for all [Entity] instances in this collection and returns this collection.
*
* **See also:** [Eager Loading](https://github.com/JetBrains/Exposed/wiki/DAO#eager-loading)
*
* @param relations The reference fields of the entities, as [KProperty]s, which should be loaded.
* @sample org.jetbrains.exposed.sql.tests.shared.entities.EntityTests.preloadRelationAtDepth
*/
fun , SRC : Entity, REF : Entity<*>, L : Iterable> L.with(vararg relations: KProperty1): L {
toList().apply {
(this@with as? LazySizedIterable)?.loadedResult = this
if (any { it.isNewEntity() }) {
TransactionManager.current().flushCache()
}
preloadRelations(*relations)
}
return this
}
/**
* Eager loads references for this [Entity] instance and returns this entity instance.
*
* **See also:** [Eager Loading](https://github.com/JetBrains/Exposed/wiki/DAO#eager-loading)
*
* @param relations The reference fields of this entity, as [KProperty]s, which should be loaded.
* @sample org.jetbrains.exposed.sql.tests.shared.entities.EntityTests.preloadOptionalReferencesOnAnEntity
*/
fun , SRC : Entity> SRC.load(vararg relations: KProperty1, Any?>): SRC = apply {
listOf(this).with(*relations)
}
internal fun hasSingleReferenceWithReferee(allReferences: Map, Column<*>>?): Boolean {
return allReferences?.size == 1 && allReferences.values.first().table !is CompositeIdTable
}
internal fun allReferencesMatch(allReferences: Map, Column<*>>, parentTable: IdTable<*>): Boolean {
val parentIdColumns = parentTable.idColumns
return allReferences.values.size == parentIdColumns.size && allReferences.values.containsAll(parentIdColumns)
}
@Suppress("UNCHECKED_CAST")
internal fun getCompositeID(entries: () -> List, *>>): CompositeID = CompositeID {
entries().forEach { (key, value) ->
it[key as Column>>] = value as Comparable
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy