Files
solutions.bitbadger.documents/src/main/kotlin/Parameters.kt

118 lines
4.6 KiB
Kotlin

package solutions.bitbadger.documents
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLException
/**
* 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.parameterName.isNullOrEmpty() && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(it.comparison.op)) {
it.withParameterName(name.derive(null))
} else {
it
}
}
}
/**
* Create a parameter by encoding a JSON object
*
* @param name The parameter name
* @param value The object to be encoded as JSON
* @return A parameter with the value encoded
*/
inline fun <reified T> json(name: String, value: T) =
Parameter(name, ParameterType.JSON, Configuration.json.encodeToString(value))
/**
* Add field parameters to the given set of parameters
*
* @param fields The fields being compared in the query
* @param existing Any existing parameters for the query (optional, defaults to empty collection)
* @return A collection of parameters for the query
*/
fun addFields(fields: Collection<Field<*>>, existing: MutableCollection<Parameter<*>> = mutableListOf()) =
fields.fold(existing) { acc, field -> field.appendParameter(acc) }
/**
* 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 -> param.bind(stmt, index + 1) }
}
} catch (ex: SQLException) {
throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex)
}
}
/**
* Create parameters for field names to be removed from a document
*
* @param names The names of the fields to be removed
* @param parameterName The parameter name to use for the query
* @return A list of parameters to use for building the query
*/
fun fieldNames(names: Collection<String>, parameterName: String = ":name") =
when (Configuration.dialect("generate field name parameters")) {
Dialect.POSTGRESQL -> listOf(
Parameter(parameterName, ParameterType.STRING, names.joinToString(",").let { "{$it}" })
)
Dialect.SQLITE -> names.mapIndexed { index, name ->
Parameter("$parameterName$index", ParameterType.STRING, name)
}
}
}