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
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 <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) }
}
fun Connection.update(template: StringTemplate): Int {
fun Connection.update(template : StringTemplate) : Int {
val preparedStatement = prepare(template)
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)
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<Binding>()
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"] }
}