org.jetbrains.exposed.dao.Entity.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.exceptions.EntityNotFoundException
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
open class ColumnWithTransform(val column: Column, val toColumn: (TReal) -> TColumn, val toReal: (TColumn) -> TReal)
open class Entity>(val id: EntityID) {
var klass: EntityClass> by Delegates.notNull()
internal set
var db: Database by Delegates.notNull()
internal set
val writeValues = LinkedHashMap, Any?>()
var _readValues: ResultRow? = null
val readValues: ResultRow
get() = _readValues ?: run {
val table = klass.table
_readValues = klass.searchQuery(Op.build { table.id eq id }).firstOrNull() ?: table.select { table.id eq id }.first()
_readValues!!
}
private val referenceCache by lazy { HashMap, Any?>() }
internal fun isNewEntity(): Boolean {
val cache = TransactionManager.current().entityCache
return cache.inserts[klass.table]?.contains(this) ?: false
}
/**
* Updates entity fields from database.
* Override function to refresh some additional state if any.
*
* @param flush whether pending entity changes should be flushed previously
* @throws EntityNotFoundException if entity no longer exists in database
*/
open fun refresh(flush: Boolean = false) {
val transaction = TransactionManager.current()
val cache = transaction.entityCache
val isNewEntity = isNewEntity()
when {
isNewEntity && flush -> cache.flushInserts(klass.table)
flush -> flush()
isNewEntity -> throw EntityNotFoundException(this.id, this.klass)
else -> writeValues.clear()
}
klass.removeFromCache(this)
val reloaded = klass[id]
cache.store(this)
_readValues = reloaded.readValues
db = transaction.db
}
internal fun getReferenceFromCache(ref: Column<*>): T {
return referenceCache[ref] as T
}
internal fun storeReferenceInCache(ref: Column<*>, value: Any?) {
if (db.config.keepLoadedReferencesOutOfTransaction) {
referenceCache[ref] = value
}
}
operator fun , RID : Comparable, T : Entity> Reference.getValue(
o: Entity,
desc: KProperty<*>
): T {
val outOfTransaction = TransactionManager.currentOrNull() == null
if (outOfTransaction && reference in referenceCache) return getReferenceFromCache(reference)
return executeAsPartOfEntityLifecycle {
val refValue = reference.getValue(o, desc)
when {
refValue is EntityID<*> && reference.referee() == factory.table.id -> {
factory.findById(refValue.value as RID).also {
storeReferenceInCache(reference, it)
}
}
else -> {
// @formatter:off
factory.findWithCacheCondition({
reference.referee!!.getValue(this, desc) == refValue
}) {
reference.referee()!! eq refValue
}.singleOrNull()?.also {
storeReferenceInCache(reference, it)
}
// @formatter:on
}
} ?: error("Cannot find ${factory.table.tableName} WHERE id=$refValue")
}
}
operator fun , RID : Comparable, T : Entity> Reference.setValue(
o: Entity,
desc: KProperty<*>,
value: T
) {
if (db != value.db) error("Can't link entities from different databases.")
value.id.value // flush before creating reference on it
val refValue = value.run { reference.referee()!!.getValue(this, desc) }
storeReferenceInCache(reference, value)
reference.setValue(o, desc, refValue)
}
operator fun , RID : Comparable, T : Entity> OptionalReference.getValue(
o: Entity,
desc: KProperty<*>
): T? {
val outOfTransaction = TransactionManager.currentOrNull() == null
if (outOfTransaction && reference in referenceCache) return getReferenceFromCache(reference)
return executeAsPartOfEntityLifecycle {
val refValue = reference.getValue(o, desc)
when {
refValue == null -> null
refValue is EntityID<*> && reference.referee() == factory.table.id -> {
factory.findById(refValue.value as RID).also {
storeReferenceInCache(reference, it)
}
}
else -> {
// @formatter:off
factory.findWithCacheCondition({
reference.referee!!.getValue(this, desc) == refValue
}) {
reference.referee()!! eq refValue
}.singleOrNull().also {
storeReferenceInCache(reference, it)
}
// @formatter:on
}
}
}
}
operator fun , RID : Comparable, T : Entity> OptionalReference.setValue(
o: Entity,
desc: KProperty<*>,
value: T?
) {
if (value != null && db != value.db) error("Can't link entities from different databases.")
value?.id?.value // flush before creating reference on it
val refValue = value?.run { reference.referee()!!.getValue(this, desc) }
storeReferenceInCache(reference, value)
reference.setValue(o, desc, refValue)
}
operator fun Column.getValue(o: Entity, desc: KProperty<*>): T = lookup()
operator fun CompositeColumn.getValue(o: Entity, desc: KProperty<*>): T {
val values = this.getRealColumns().associateWith { it.lookup() }
return this.restoreValueFromParts(values)
}
@Suppress("UNCHECKED_CAST")
fun Column.lookupInReadValues(found: (T?) -> R?, notFound: () -> R?): R? =
if (_readValues?.hasValue(this) == true) {
found(readValues[this])
} else {
notFound()
}
@Suppress("UNCHECKED_CAST", "USELESS_CAST")
fun Column.lookup(): T = when {
writeValues.containsKey(this as Column) -> writeValues[this as Column] as T
id._value == null && _readValues?.hasValue(this)?.not() ?: true -> defaultValueFun?.invoke() as T
columnType.nullable -> readValues[this]
else -> readValues[this]!!
}
operator fun Column.setValue(o: Entity, desc: KProperty<*>, value: T) {
klass.invalidateEntityInCache(o)
val currentValue = _readValues?.getOrNull(this)
if (writeValues.containsKey(this as Column) || currentValue != value) {
val entityCache = TransactionManager.current().entityCache
if (referee != null) {
if (value is EntityID<*> && value.table == referee!!.table) value.value // flush
listOfNotNull(value, currentValue).forEach {
entityCache.referrers[this]?.remove(it)
}
}
writeValues[this as Column] = value
// TODO: Can this be simplified?
if (o.id._value?.let { entityCache.data[table]?.contains(it) } == true) {
entityCache.scheduleUpdate(klass, o)
}
}
}
operator fun CompositeColumn.setValue(o: Entity, desc: KProperty<*>, value: T) {
with(o) {
[email protected](value).forEach {
(it.key as Column).setValue(o, desc, it.value)
}
}
}
operator fun ColumnWithTransform.getValue(o: Entity, desc: KProperty<*>): TReal =
toReal(column.getValue(o, desc))
operator fun ColumnWithTransform.setValue(o: Entity, desc: KProperty<*>, value: TReal) {
column.setValue(o, desc, toColumn(value))
}
infix fun , Target : Entity> EntityClass.via(table: Table): InnerTableLink, TID, Target> =
InnerTableLink(table, this@via)
fun , Target : Entity> EntityClass.via(
sourceColumn: Column>,
targetColumn: Column>
) =
InnerTableLink(sourceColumn.table, this@via, sourceColumn, targetColumn)
/**
* Delete this entity.
*
* This will remove the entity from the database as well as the cache.
*/
open fun delete() {
val table = klass.table
TransactionManager.current().registerChange(klass, id, EntityChangeType.Removed)
executeAsPartOfEntityLifecycle {
table.deleteWhere { table.id eq id }
}
klass.removeFromCache(this)
}
open fun flush(batch: EntityBatchUpdate? = null): Boolean {
if (isNewEntity()) {
TransactionManager.current().entityCache.flushInserts(this.klass.table)
return true
}
if (writeValues.isNotEmpty()) {
if (batch == null) {
val table = klass.table
// Store values before update to prevent flush inside UpdateStatement
val _writeValues = writeValues.toMap()
storeWrittenValues()
// In case of batch all changes will be registered after all entities flushed
TransactionManager.current().registerChange(klass, id, EntityChangeType.Updated)
executeAsPartOfEntityLifecycle {
table.update({ table.id eq id }) {
for ((c, v) in _writeValues) {
it[c] = v
}
}
}
} else {
batch.addBatch(id)
for ((c, v) in writeValues) {
batch[c] = v
}
storeWrittenValues()
}
return true
}
return false
}
fun storeWrittenValues() {
// move write values to read values
if (_readValues != null) {
for ((c, v) in writeValues) {
_readValues!![c] = v
}
if (klass.dependsOnColumns.any { it.table == klass.table && !_readValues!!.hasValue(it) }) {
_readValues = null
}
}
// clear write values
writeValues.clear()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy