From bcde2f954433a95f404f644a09550dc882a96137 Mon Sep 17 00:00:00 2001 From: James Strachan Date: Wed, 14 Mar 2012 18:18:46 +0000 Subject: [PATCH] added hook for caching preparedStatements along with simplifying the logic of using PreparedStatements as we make the statement up front, then bind directly. Also support most of the common data types (though need more null support) --- .../test/kotlin/jdbc/JdbcTemplateTest.kt | 193 ++++++++++++------ 1 file changed, 130 insertions(+), 63 deletions(-) diff --git a/libraries/kotlin-jdbc/src/test/kotlin/test/kotlin/jdbc/JdbcTemplateTest.kt b/libraries/kotlin-jdbc/src/test/kotlin/test/kotlin/jdbc/JdbcTemplateTest.kt index 0712aeb5e21..41d2e2c90be 100644 --- a/libraries/kotlin-jdbc/src/test/kotlin/test/kotlin/jdbc/JdbcTemplateTest.kt +++ b/libraries/kotlin-jdbc/src/test/kotlin/test/kotlin/jdbc/JdbcTemplateTest.kt @@ -1,11 +1,11 @@ package test.kotlin.jdbc.experiment1 -import kotlin.template.* -import kotlin.jdbc.* -import kotlin.test.* -import kotlin.util.* +import kotlin.template. * +import kotlin.jdbc. * +import kotlin.test. * +import kotlin.util. * -import test.kotlin.jdbc.* +import test.kotlin.jdbc. * import junit.framework.TestCase import javax.sql.DataSource @@ -13,104 +13,171 @@ import javax.sql.DataSource import java.sql.Connection import java.sql.PreparedStatement import java.sql.ResultSet +import java.math.BigDecimal +import java.sql.Date +import java.sql.Time +import java.sql.Timestamp -fun DataSource.update(template: StringTemplate): Int { +fun DataSource.update(template : StringTemplate) : Int { return use{ it.update(template) } } -fun DataSource.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T { +fun DataSource.query(template : StringTemplate, resultBlock : (ResultSet) -> T) : T { return use{ it.query(template, resultBlock) } } -fun Connection.update(template: StringTemplate): Int { +fun Connection.update(template : StringTemplate) : Int { val preparedStatement = prepare(template) return preparedStatement.update() } -fun Connection.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T { +fun Connection.query(template : StringTemplate, resultBlock : (ResultSet) -> T) : T { val preparedStatement = prepare(template) return preparedStatement.query(resultBlock) } -fun Connection.prepare(template: StringTemplate): PreparedStatement { - val builder = PreparedStatementBuilder(this) - var constantText = true - template.forEach{ - if (constantText) { - if (it == null) { - throw IllegalStateException("No constant checks should be null"); - } else { - val text = it.toString() - if (text != null) { - builder.text(text) - } - } - } else { - builder.expression(it) - } - constantText = !constantText - } - return builder.statement() +fun Connection.prepare(template : StringTemplate) : PreparedStatement { + val builder = PreparedStatementBuilder(template, this) + builder.bind() + return builder.statement } trait Binding { - fun bind(statement: PreparedStatement): Unit + fun bind(statement : PreparedStatement) : Unit } -class PreparedStatementBuilder(val connection: Connection) { - val sql = StringBuilder() - val tasks = arrayList() +class PreparedStatementBuilder(val template : StringTemplate, val connection : Connection) { private var parameterIndex = 0 - fun text(value: String): Unit { - // we assume all text is escaped already - sql.append(value) - } + public val sql : String = createSql() - fun expression(value: Any?): Unit { - if (value is String) { - expression(value) - } else if (value is Int) { - expression(value) - } else { + public val statement: PreparedStatement = lookupOrCreateStatement() + /** + * Binds the values in the [[StringTemplate]] to the [[PreparedStatement]] + */ + fun bind() { + var constantText = true + template.forEach{ + if (!constantText) { + expression(it) + } + constantText = !constantText } } - fun expression(value: String): Unit { - sql.append("?") - tasks.add(object: Binding { - override fun bind(statement : PreparedStatement) { - statement.setString(nextParameterIndex(), value) + fun expression(value : Any?) : Unit { + // TODO causes compiler error + // if (value is Number) { + if (value is Int) { + expression(value) + } else if (value is Double) { + expression(value) + } else if (value is Float) { + expression(value) + } else if (value is BigDecimal) { + expression(value) + } else if (value is Byte) { + expression(value) + } else if (value is Long) { + expression(value) + } else if (value is Short) { + expression(value) + /* + } else { + expression(value.toDouble()) } - }) + */ + } + else if (value is String) { + expression(value) + } else if (value is ByteArray) { + expression(value) + } else if (value is Date) { + expression(value) + } else if (value is Time) { + expression(value) + } else if (value is Timestamp) { + expression(value) + } else { + statement.setObject(nextParameterIndex(), value) + } } - fun expression(value: Int): Unit { - sql.append("?") - tasks.add(object: Binding { - override fun bind(statement : PreparedStatement) { - statement.setInt(nextParameterIndex(), value) - } - }) + fun expression(value : String?) : Unit { + statement.setString(nextParameterIndex(), value) + } + + fun expression(value : Int) : Unit { + statement.setInt(nextParameterIndex(), value) + } + + fun expression(value : BigDecimal?) : Unit { + statement.setBigDecimal(nextParameterIndex(), value) + } + + fun expression(value : Byte) : Unit { + statement.setByte(nextParameterIndex(), value) + } + + fun expression(value : Double) : Unit { + statement.setDouble(nextParameterIndex(), value) + } + + fun expression(value : Float) : Unit { + statement.setFloat(nextParameterIndex(), value) + } + + fun expression(value : Long) : Unit { + statement.setLong(nextParameterIndex(), value) + } + + fun expression(value : Short) : Unit { + statement.setShort(nextParameterIndex(), value) + } + + fun expression(value : ByteArray) : Unit { + statement.setBytes(nextParameterIndex(), value) + } + + fun expression(value : Date) : Unit { + statement.setDate(nextParameterIndex(), value) + } + + fun expression(value : Time) : Unit { + statement.setTime(nextParameterIndex(), value) + } + + fun expression(value : Timestamp) : Unit { + statement.setTimestamp(nextParameterIndex(), value) } // TODO bind other kinds! - fun statement(): PreparedStatement { - val answer = connection.prepareStatement(sql.toString()) + /** + * Looks up the [[PreparedStatement]] in a cache or creates a new one + */ + protected fun lookupOrCreateStatement(): PreparedStatement { + val answer = connection.prepareStatement(sql) if (answer == null) { throw IllegalStateException("No PreparedStatement returned from $connection") } else { - for (task in tasks) { - task.bind(answer) - } return answer } } - protected fun nextParameterIndex(): Int = ++parameterIndex + protected fun nextParameterIndex() : Int = ++parameterIndex + + protected fun createSql() : String { + val out = StringBuilder() + var constantText = true + template.forEach { + out.append(if (constantText) it else "?") + constantText = !constantText + } + return out.toString() ?: "" + } } @@ -126,14 +193,14 @@ class JdbcTemplateTest : TestCase() { // TODO will use a tuple soon dataSource.update( - StringTemplate(array("insert into foo (id, name) values (", id, ", ", name, ")")) + StringTemplate(array("insert into foo (id, name) values (", id, ", ", name, ")")) ) // Mimicks // datasource.query("select * from foo where id = $id") { it.map{ it["name"] } } val names = dataSource.query( - StringTemplate(array("select * from foo where id = ", id)) + StringTemplate(array("select * from foo where id = ", id)) ) { it.map{ it["name"] } }