97 lines
4.2 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|