From 5b1f3e32832db77c30c0c3006fc84a8f9fda1710 Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sat, 4 Aug 2018 17:18:34 +0300 Subject: [PATCH] implemented RecordManager.getDirty() and Session.update(), #32 --- .../net/aquadc/properties/sql/jdbc-sqlite.kt | 65 ++++++++++++++++--- .../net/aquadc/properties/sql/library.kt | 4 +- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/sql/src/main/kotlin/net/aquadc/properties/sql/jdbc-sqlite.kt b/sql/src/main/kotlin/net/aquadc/properties/sql/jdbc-sqlite.kt index 4afe5e3f..eec8be26 100644 --- a/sql/src/main/kotlin/net/aquadc/properties/sql/jdbc-sqlite.kt +++ b/sql/src/main/kotlin/net/aquadc/properties/sql/jdbc-sqlite.kt @@ -33,16 +33,17 @@ class JdbcSqliteSession(private val connection: Connection) : Session { // transactional things, guarded by write-lock private var transaction: JdbcTransaction? = null private val insertStatements = HashMap, List>>, PreparedStatement>() + private val updateStatements = HashMap, Col<*, *>>, PreparedStatement>() - private fun > insertStatementWLocked(table: Table, cols: Array>): PreparedStatement { - val cacheKey = Pair(table, cols.asList()) - val cached = insertStatements[cacheKey] - if (cached != null) return cached + private fun > insertStatementWLocked(table: Table, cols: Array>): PreparedStatement = + insertStatements.getOrPut(Pair(table, cols.asList())) { + connection.prepareStatement(insertQuery(table, cols), Statement.RETURN_GENERATED_KEYS) + } - val stmt = connection.prepareStatement(insertQuery(table, cols), Statement.RETURN_GENERATED_KEYS) - insertStatements[cacheKey] = stmt - return stmt - } + private fun > updateStatementWLocked(table: Table, col: Col): PreparedStatement = + updateStatements.getOrPut(Pair(table, col)) { + connection.prepareStatement(updateQuery(table, col)) + } override fun beginTransaction(): Transaction { val wLock = lock.writeLock() @@ -75,6 +76,12 @@ class JdbcSqliteSession(private val connection: Connection) : Session { override val session: Session get() = this@JdbcSqliteSession + // table : primary keys + internal var inserted: HashMap, ArrayList>? = null + + // column : primary key : value + internal var updated: HashMap, HashMap>? = null + override fun , ID : IdBound> insert(table: Table, vararg contentValues: ColValue): ID { checkOpenAndThread() @@ -89,9 +96,32 @@ class JdbcSqliteSession(private val connection: Connection) : Session { check(statement.executeUpdate() == 1) val keys = statement.generatedKeys val id: ID = keys.fetchSingle() + + inserted ?: HashMap, ArrayList>().also { inserted = it } + .getOrPut(table, ::ArrayList) + .add(id) + + // writes all insertion fields as updates + val updated = updated ?: HashMap, HashMap>().also { updated = it } + contentValues.forEach { + updated.getOrPut(it.col, ::HashMap)[it.col] = it.value + } + return id } + override fun , ID : IdBound, T> update(table: Table, id: ID, column: Col, value: T) { + checkOpenAndThread() + + val statement = updateStatementWLocked(table, column) + statement.setObject(1, value) + statement.setObject(2, id) + check(statement.executeUpdate() == 1) + + (updated ?: HashMap, HashMap>().also { updated = it }) + .getOrPut(column, ::HashMap)[id] = value + } + override fun setSuccessful() { isSuccessful = true } @@ -186,6 +216,10 @@ class JdbcSqliteSession(private val connection: Connection) : Session { else -> TODO("${id.javaClass} keys support") } + private fun dbId(table: Table<*, ID>, localId: Long): ID = + localId as ID + + private inner class RecordManager : Manager, Any?> { /* @@ -206,7 +240,11 @@ class JdbcSqliteSession(private val connection: Connection) : Session { private val reusableCond = ThreadLocal>() override fun getDirty(token: Col<*, *>, id: Long): Any? { - return Unset // todo + val transaction = transaction ?: return Unset + + val thisCol = transaction.updated?.get(token) ?: return Unset + val primaryKey = dbId(token.table, id) + return if (thisCol.containsKey(primaryKey)) thisCol[primaryKey] else Unset } @Suppress("UPPER_BOUND_VIOLATED") @@ -221,8 +259,10 @@ class JdbcSqliteSession(private val connection: Connection) : Session { .fetchSingle() } + @Suppress("UPPER_BOUND_VIOLATED") override fun set(token: Col<*, *>, id: Long, expected: Any?, update: Any?, onTransactionEnd: (newValue: Any?) -> Unit): Boolean { - TODO("set") + transaction!!.update(token.table as Table, dbId(token.table, id), token as Col, update) // TODO: check expected + return true } } @@ -243,6 +283,11 @@ private fun > insertQuery(table: Table, cols: Array .append(" (").appendNames(cols).append(") VALUES (").appendPlaceholders(cols.size).append(");") .toString() +private fun > updateQuery(table: Table, col: Col): String = + StringBuilder("UPDATE ").appendName(table.name) + .append(" SET ").appendName(col.name).append(" = ? WHERE ").appendName(table.idCol.name).append(" = ?;") + .toString() + private fun > selectQuery(column: Col?, table: Table, condition: WhereCondition): String { val sb = StringBuilder("SELECT ") .let { if (column == null) it.append("COUNT(*)") else it.appendName(column.name) } diff --git a/sql/src/main/kotlin/net/aquadc/properties/sql/library.kt b/sql/src/main/kotlin/net/aquadc/properties/sql/library.kt index 7c2ecfc0..172e85fe 100644 --- a/sql/src/main/kotlin/net/aquadc/properties/sql/library.kt +++ b/sql/src/main/kotlin/net/aquadc/properties/sql/library.kt @@ -46,6 +46,8 @@ interface Transaction : AutoCloseable { fun , ID : IdBound> insert(table: Table, vararg contentValues: ColValue): ID + fun , ID : IdBound, T> update(table: Table, id: ID, column: Col, value: T) + fun setSuccessful() } @@ -129,7 +131,7 @@ class Col, out T>( abstract class Record, ID : IdBound>( private val table: Table, private val session: Session, - private val primaryKey: ID + val primaryKey: ID ) { @Suppress("UNCHECKED_CAST") // id is not nullable, so ForeREC won't be, too