Skip to content

Commit

Permalink
Entity queried from database after creation on any prop access
Browse files Browse the repository at this point in the history
Prevent unneeded referrers cache flushes
  • Loading branch information
Tapac committed Nov 18, 2015
1 parent 670de6a commit d5bef3c
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 23 deletions.
38 changes: 24 additions & 14 deletions src/kotlin/dao/Entity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,32 @@ open public class Entity(val id: EntityID) {
return lookup()
}

@Suppress("UNCHECKED_CAST")
fun <T, R:Any> Column<T>.lookupInReadValues(found: (T?) -> R?, notFound: () -> R?): R? {
if (_readValues?.contains(this) ?: false )
return found(readValues.tryGet(this))
if (_readValues?.hasValue(this) ?: false)
return found(readValues[this])
else
return notFound()
}

@Suppress("UNCHECKED_CAST")
fun <T:Any?> Column<T>.lookup(): T = when {
writeValues.containsKey(this) -> writeValues[this] as T
id._value == -1 && _readValues?.contains(this)?.not() ?: true -> {
defaultValue as T
}
writeValues.containsKeyRaw(this) -> writeValues.getRaw(this) as T
id._value == -1 && _readValues?.hasValue(this)?.not() ?: true -> defaultValue as T
else -> readValues[this]
}

operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T) {
if (writeValues.containsKey(this) || _readValues == null || _readValues!![this] != value) {
if (writeValues.containsKeyRaw(this) || _readValues?.tryGet(this) != value) {
if (referee != null) {
EntityCache.getOrCreate(Session.get()).clearReferrersCache()
EntityCache.getOrCreate(Session.get()).referrers.run {
filterKeys { it.id == value }.forEach {
if (it.value.keys.any { it == this@setValue } ) {
this.remove(it.key)
}
}
}
EntityCache.getOrCreate(Session.get()).removeTablesReferrers(listOf(referee!!.table))
}
writeValues.set(this as Column<Any?>, value)
}
Expand Down Expand Up @@ -271,10 +277,12 @@ open public class Entity(val id: EntityID) {
// move write values to read values
if (_readValues != null) {
for ((c, v) in writeValues) {
_readValues!!.set(c, v)
_readValues!!.set(c, v?.let { c.columnType.valueFromDB(c.columnType.valueToDB(it)!!)})
}
if (klass.dependsOnColumns.any { it.table == klass.table && !_readValues!!.hasValue(it) } ) {
_readValues = null
}
}

// clear write values
writeValues.clear()
}
Expand Down Expand Up @@ -419,7 +427,7 @@ class EntityCache {
}
}

internal fun removeTablesReferrers(insertedTables: List<IdTable>) {
internal fun removeTablesReferrers(insertedTables: List<Table>) {
referrers.filterValues { it.any { it.key.table in insertedTables } }.map { it.key }.forEach {
referrers.remove(it)
}
Expand All @@ -431,11 +439,12 @@ class EntityCache {
for ((c, v) in entry.writeValues) {
this[c] = v
}
entry.storeWrittenValues()
}

for ((entry, id) in it.zip(ids)) {
entry.id._value = id
entry.writeValues.set(entry.klass.table.id as Column<Any?>, id)
entry.storeWrittenValues()
EntityCache.getOrCreate(Session.get()).store(table, entry)
EntityHook.alertSubscribers(entry, true)
}
Expand Down Expand Up @@ -498,7 +507,7 @@ abstract public class EntityClass<out T: Entity>(val table: IdTable) {
val cache = warmCache()
cache.remove(table, entity)
cache.referrers.remove(entity)
cache.removeTablesReferrers(listOf(entity.klass.table))
cache.removeTablesReferrers(listOf(table))
}

public fun forEntityIds(ids: List<EntityID>) : SizedIterable<T> {
Expand Down Expand Up @@ -529,6 +538,7 @@ abstract public class EntityClass<out T: Entity>(val table: IdTable) {
val entity = wrap(row[table.id], row, session)
if (entity._readValues == null)
entity._readValues = row

return entity
}

Expand Down Expand Up @@ -580,8 +590,8 @@ abstract public class EntityClass<out T: Entity>(val table: IdTable) {
public fun new(init: T.() -> Unit): T {
val prototype: T = createInstance(EntityID(-1, table), null)
prototype.klass = this
prototype._readValues = ResultRow.create(dependsOnColumns)
prototype.init()

warmCache().scheduleInsert(this, prototype)
return prototype
}
Expand Down
2 changes: 1 addition & 1 deletion src/kotlin/sql/Column.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ open class Column<T>(val table: Table, val name: String, override val columnType
}

override fun toString(): String {
return "$table.$name"
return "${table.javaClass.name}.$name"
}

override fun toSQL(queryBuilder: QueryBuilder): String {
Expand Down
6 changes: 5 additions & 1 deletion src/kotlin/sql/DeleteQuery.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package kotlin.sql

import kotlin.dao.EntityCache

object DeleteQuery {
fun where(session: Session, table: Table, op: Op<Boolean>, isIgnore: Boolean = false): Int {
val ignore = if (isIgnore && Session.get().vendor == DatabaseVendor.MySql) "IGNORE" else ""
val builder = QueryBuilder(true)
val sql = StringBuilder("DELETE $ignore FROM ${session.identity(table)} WHERE ${op.toSQL(builder)}")
return builder.executeUpdate(session, sql.toString())
return builder.executeUpdate(session, sql.toString()).apply {
EntityCache.getOrCreate(Session.get()).removeTablesReferrers(listOf(table))
}
}

fun all(session: Session, table: Table): Int {
Expand Down
27 changes: 20 additions & 7 deletions src/kotlin/sql/Query.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ public class ResultRow(size: Int, private val fieldIndex: Map<Expression<*>, Int
*/
@Suppress("UNCHECKED_CAST")
operator fun <T> get(c: Expression<T>) : T {
val d:Any? = when {
fieldIndex.containsKey(c) -> data[fieldIndex[c]!!]
else -> error("${c.toSQL(QueryBuilder(false))} is not in record set")
}
val d:Any? = getRaw(c)

return d?.let {
if (d == NotInitializedValue) error("${c.toSQL(QueryBuilder(false))} is not initialized yet")
(c as? ExpressionWithColumnType<*>)?.columnType?.valueFromDB(it) ?: it
} as T
}
Expand All @@ -29,19 +27,27 @@ public class ResultRow(size: Int, private val fieldIndex: Map<Expression<*>, Int
}

fun<T> hasValue (c: Expression<T>) : Boolean {
return fieldIndex[c]?.let{data[it]} != null;
return fieldIndex[c]?.let{ data[it] != NotInitializedValue } ?: false;
}

fun contains(c: Expression<*>) = fieldIndex.containsKey(c)

fun <T> tryGet(c: Expression<T>): T? {
return if (hasValue(c)) get(c) else null
}

@Suppress("UNCHECKED_CAST")
private fun <T> getRaw(c: Expression<T>): T? {
val nullable = c is PKColumn<*> || (c as? Column<T>)?.columnType?.nullable ?: false
val value = fieldIndex[c]?.let { data[it] }
if (hasValue(c) && value == null && !nullable) error("${c.toSQL(QueryBuilder(false))} is not in record set")
return value as T?
}

override fun toString(): String {
return fieldIndex.map { "${it.key.toSQL(QueryBuilder(false))}=${data[it.value]}" }.joinToString()
}

internal object NotInitializedValue;

companion object {
fun create(rs: ResultSet, fields: List<Expression<*>>, fieldsIndex: Map<Expression<*>, Int>) : ResultRow {
val size = fieldsIndex.size
Expand All @@ -55,6 +61,13 @@ public class ResultRow(size: Int, private val fieldIndex: Map<Expression<*>, Int
}
return answer
}

internal fun create(columns : List<Column<*>>): ResultRow =
ResultRow(columns.size, columns.mapIndexed { i, c -> c to i }.toMap()).apply {
columns.forEach {
this[it] = it.defaultValue ?: if (!it.columnType.nullable) NotInitializedValue else null
}
}
}
}

Expand Down

0 comments on commit d5bef3c

Please sign in to comment.