97 lines
4.2 KiB
Kotlin

package solutions.bitbadger.documents
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLException
import java.sql.Types
/**
* Functions to assist with the creation and implementation of parameters for SQL queries
*
* @author Daniel J. Summers <daniel@bitbadger.solutions>
*/
object Parameters {
/**
* Assign parameter names to any fields that do not have them assigned
*
* @param fields The collection of fields to be named
* @return The collection of fields with parameter names assigned
*/
fun nameFields(fields: Collection<Field<*>>): Collection<Field<*>> {
val name = ParameterName()
return fields.map {
if (it.name.isBlank()) it.withParameterName(name.derive(null)) else it
}
}
/**
* Replace the parameter names in the query with question marks
*
* @param query The query with named placeholders
* @param parameters The parameters for the query
* @return The query, with name parameters changed to `?`s
*/
fun replaceNamesInQuery(query: String, parameters: Collection<Parameter<*>>) =
parameters.sortedByDescending { it.name.length }.fold(query) { acc, param -> acc.replace(param.name, "?") }
/**
* Apply the given parameters to the given query, returning a prepared statement
*
* @param conn The active JDBC connection
* @param query The query
* @param parameters The parameters for the query
* @return A `PreparedStatement` with the parameter names replaced with `?` and parameter values bound
* @throws DocumentException If parameter names are invalid or number value types are invalid
*/
fun apply(conn: Connection, query: String, parameters: Collection<Parameter<*>>): PreparedStatement {
if (parameters.isEmpty()) return try {
conn.prepareStatement(query)
} catch (ex: SQLException) {
throw DocumentException("Error preparing no-parameter query: ${ex.message}", ex)
}
val replacements = mutableListOf<Pair<Int, Parameter<*>>>()
parameters.sortedByDescending { it.name.length }.forEach {
var startPos = query.indexOf(it.name)
while (startPos > -1) {
replacements.add(Pair(startPos, it))
startPos = query.indexOf(it.name, startPos + it.name.length + 1)
}
}
return try {
replaceNamesInQuery(query, parameters)
.let { conn.prepareStatement(it) }
.also { stmt ->
replacements.sortedBy { it.first }.map { it.second }.forEachIndexed { index, param ->
val idx = index + 1
when (param.type) {
ParameterType.NUMBER -> {
when (param.value) {
null -> stmt.setNull(idx, Types.NULL)
is Byte -> stmt.setByte(idx, param.value)
is Short -> stmt.setShort(idx, param.value)
is Int -> stmt.setInt(idx, param.value)
is Long -> stmt.setLong(idx, param.value)
else -> throw DocumentException(
"Number parameter must be Byte, Short, Int, or Long (${param.value::class.simpleName})")
}
}
ParameterType.STRING -> {
when (param.value) {
null -> stmt.setNull(idx, Types.NULL)
is String -> stmt.setString(idx, param.value)
else -> stmt.setString(idx, param.value.toString())
}
}
ParameterType.JSON -> stmt.setString(idx, Configuration.json.encodeToString(param.value))
}
}
}
} catch (ex: SQLException) {
throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex)
}
}
}