118 lines
4.6 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|