Skip to content

Instantly share code, notes, and snippets.

@peterbb
Last active February 15, 2022 10:13
Show Gist options
  • Save peterbb/8f2e237fa72627fe072ebf7ae796e3f2 to your computer and use it in GitHub Desktop.
Save peterbb/8f2e237fa72627fe072ebf7ae796e3f2 to your computer and use it in GitHub Desktop.
Named, typed parameters for java.sql.PreparedStatement in Kotlin
object GetUser: Template() {
var id by SqlInt
var name by SqlString
override fun query() = """
(select * from users where name = $name and id = $id)
union
(select * from deleted_user where id = $id)
"""
}
fun java.sql.Connection.getUser(name: String, id: Int): java.sql.PreparedStatement =
prepareStatement(GetUser) {
parameter.name = inject(name)
parameter.id = inject(id)
}
import java.sql.Connection
import java.sql.PreparedStatement
import kotlin.reflect.KProperty
abstract class Template {
abstract fun query(): String
private var index = 1
private val variables = mutableMapOf<String, MutableList<Int>>()
private var compiledQuery: String? = null
private fun <T> getValue(name: String): Parameter<T> {
require(compiledQuery == null)
variables
.getOrPut(name) { mutableListOf() }
.add(index++)
return Parameter.Placeholder()
}
private fun <T> setValue(
name: String,
setter: (PreparedStatement, Int, T) -> Unit,
parameter: Parameter<T>
) {
require(compiledQuery != null)
when (parameter) {
is Parameter.Placeholder -> error("nope")
is Parameter.Value -> {
val indexes = variables[name] ?: error("'$name' not set")
for (i in indexes) {
setter(parameter.statement, i, parameter.value)
}
}
}
}
open class ParameterDelegate<T : Any>(
private val setter: (PreparedStatement, Int, T) -> Unit
) {
operator fun getValue(template: Template, property: KProperty<*>): Parameter<T> {
return template.getValue(property.name)
}
operator fun setValue(template: Template, property: KProperty<*>, parameter: Parameter<T>) {
template.setValue(property.name, setter, parameter)
}
}
class ParameterInjectionContext<T : Template>(
val parameter: T,
private val preparedStatement: PreparedStatement,
) {
fun <X> inject(x: X) = Parameter.Value(x, preparedStatement)
}
sealed interface Parameter<T> {
class Placeholder<T> : Parameter<T> {
override fun toString(): String = "?"
}
class Value<T>(
val value: T,
val statement: PreparedStatement,
) : Parameter<T>
}
companion object {
fun <T : Template> Connection.prepareStatement(
template: T,
parameterInjection: ParameterInjectionContext<T>.() -> Unit
): PreparedStatement {
if (template.compiledQuery == null) {
template.compiledQuery = template.query()
}
return this.prepareStatement(template.compiledQuery)
.also {
try {
ParameterInjectionContext(template, it).parameterInjection()
} catch (e: Exception) {
it.close()
throw e
}
}
}
}
}
object SqlString : Template.ParameterDelegate<String>(PreparedStatement::setString)
object SqlInt : Template.ParameterDelegate<Int>(PreparedStatement::setInt)
object SqlUUID : Template.ParameterDelegate<java.util.UUID>(PreparedStatement::setObject)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment