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)

This commit is contained in:
James Strachan
2012-03-14 18:18:46 +00:00
parent 5f2fc14b8b
commit bcde2f9544
@@ -1,11 +1,11 @@
package test.kotlin.jdbc.experiment1 package test.kotlin.jdbc.experiment1
import kotlin.template.* import kotlin.template. *
import kotlin.jdbc.* import kotlin.jdbc. *
import kotlin.test.* import kotlin.test. *
import kotlin.util.* import kotlin.util. *
import test.kotlin.jdbc.* import test.kotlin.jdbc. *
import junit.framework.TestCase import junit.framework.TestCase
import javax.sql.DataSource import javax.sql.DataSource
@@ -13,104 +13,171 @@ import javax.sql.DataSource
import java.sql.Connection import java.sql.Connection
import java.sql.PreparedStatement import java.sql.PreparedStatement
import java.sql.ResultSet 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) } return use{ it.update(template) }
} }
fun <T> DataSource.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T { fun <T> DataSource.query(template : StringTemplate, resultBlock : (ResultSet) -> T) : T {
return use{ it.query(template, resultBlock) } return use{ it.query(template, resultBlock) }
} }
fun Connection.update(template: StringTemplate): Int { fun Connection.update(template : StringTemplate) : Int {
val preparedStatement = prepare(template) val preparedStatement = prepare(template)
return preparedStatement.update() return preparedStatement.update()
} }
fun <T> Connection.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T { fun <T> Connection.query(template : StringTemplate, resultBlock : (ResultSet) -> T) : T {
val preparedStatement = prepare(template) val preparedStatement = prepare(template)
return preparedStatement.query(resultBlock) return preparedStatement.query(resultBlock)
} }
fun Connection.prepare(template: StringTemplate): PreparedStatement { fun Connection.prepare(template : StringTemplate) : PreparedStatement {
val builder = PreparedStatementBuilder(this) val builder = PreparedStatementBuilder(template, this)
var constantText = true builder.bind()
template.forEach{ return builder.statement
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()
} }
trait Binding { trait Binding {
fun bind(statement: PreparedStatement): Unit fun bind(statement : PreparedStatement) : Unit
} }
class PreparedStatementBuilder(val connection: Connection) { class PreparedStatementBuilder(val template : StringTemplate, val connection : Connection) {
val sql = StringBuilder()
val tasks = arrayList<Binding>()
private var parameterIndex = 0 private var parameterIndex = 0
fun text(value: String): Unit { public val sql : String = createSql()
// we assume all text is escaped already
sql.append(value)
}
fun expression(value: Any?): Unit { public val statement: PreparedStatement = lookupOrCreateStatement()
if (value is String) {
expression(value)
} else if (value is Int) {
expression(value)
} else {
/**
* 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 { fun expression(value : Any?) : Unit {
sql.append("?") // TODO causes compiler error
tasks.add(object: Binding { // if (value is Number) {
override fun bind(statement : PreparedStatement) { if (value is Int) {
statement.setString(nextParameterIndex(), value) 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 { fun expression(value : String?) : Unit {
sql.append("?") statement.setString(nextParameterIndex(), value)
tasks.add(object: Binding { }
override fun bind(statement : PreparedStatement) {
statement.setInt(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! // 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) { if (answer == null) {
throw IllegalStateException("No PreparedStatement returned from $connection") throw IllegalStateException("No PreparedStatement returned from $connection")
} else { } else {
for (task in tasks) {
task.bind(answer)
}
return 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 // TODO will use a tuple soon
dataSource.update( dataSource.update(
StringTemplate(array("insert into foo (id, name) values (", id, ", ", name, ")")) StringTemplate(array("insert into foo (id, name) values (", id, ", ", name, ")"))
) )
// Mimicks // Mimicks
// datasource.query("select * from foo where id = $id") { it.map{ it["name"] } } // datasource.query("select * from foo where id = $id") { it.map{ it["name"] } }
val names = dataSource.query( val names = dataSource.query(
StringTemplate(array("select * from foo where id = ", id)) StringTemplate(array("select * from foo where id = ", id))
) { ) {
it.map{ it["name"] } it.map{ it["name"] }
} }